Caching Strategies

intermediate caching cache-aside write-through write-behind patterns

A caching strategy is just the rule for who talks to the cache and when. The tricky part is never the reads — it’s keeping the cache and the database agreed on what’s true. Let’s walk the five patterns interviewers actually ask about.

Cache-aside (lazy loading)

The most common one by far. In simple language — the application manages the cache directly. The cache doesn’t know the database exists.

On a read: check the cache first. Hit? Return it. Miss? Load from the DB, stash a copy in the cache, return it. We only cache data that’s actually been asked for — hence “lazy loading”.

Cache-aside read flow
1App asks the cache for the key
HITCache returns the value → done
— or —
MISSApp reads from the database
2App writes the value back into the cache
3App returns the value → next read is a HIT
async function getUser(id) {
  const cached = await cache.get(`user:${id}`);
  if (cached) return JSON.parse(cached);      // HIT

  const user = await db.users.findById(id);   // MISS → source of truth
  await cache.set(`user:${id}`, JSON.stringify(user), "EX", 300); // 5 min TTL
  return user;
}

When to use: read-heavy workloads where the app is happy owning the logic. Downside: the first read of anything is always a miss (a “cold cache”), and our code has to remember to keep the cache in sync on writes.

Read-through

Same read behaviour as cache-aside, but the cache does the DB fetch on a miss, not our app. We only ever talk to the cache; it’s the cache’s job to fill itself from the backing store.

The only difference from cache-aside is who loads on a miss — the cache library instead of our code. It keeps the app simpler, but we need a cache that supports it (a provider or a loader function).

Write-through

Now the writes. Write-through means every write goes to the cache and the database together, synchronously, before we tell the caller “done”.

The cache is always fresh — it can never be more stale than the DB, because they’re updated in the same breath. The price is a slower write: we pay two writes on the critical path.

Write-behind (write-back)

Same idea, but we cheat on timing. The write hits the cache and returns immediately. The cache flushes to the database later — batched, async, in the background.

Writes feel instant and the DB sees far fewer, bigger writes. The danger is obvious: if the cache dies before it flushes, those writes are gone. We trade durability for speed.

Write-through
write → cache + DB together
↓ both, synchronously
return only after both succeed
safe slower writes
Write-behind (write-back)
write → cache only
↓ return NOW
cache flushes to DB later, batched
fast writes risk of data loss

Write-around

The odd one out. On a write, we skip the cache entirely and go straight to the database. The cache only fills up later, on a read miss (cache-aside style).

Why bother? Because sometimes we write data that won’t be read again soon — logs, bulk imports, one-off records. Writing it into the cache would just evict genuinely-hot data for nothing. Write-around keeps the cache full of stuff people actually read.

When to use: write-heavy data with low re-read rates. Downside: a just-written record is a guaranteed miss if someone reads it right away.

Interview soundbite

Reads: cache-aside (app fills the cache) and read-through (cache fills itself) — same behaviour, different owner. Writes: write-through is safe but slow (cache + DB together), write-behind is fast but risky (cache now, DB later), and write-around skips the cache on writes to avoid polluting it with data nobody reads. Most systems run cache-aside for reads plus write-through or write-around for writes.