System Design's caching lesson covered one pattern in full — cache-aside — because it's the one application code reaches for most often. It's not the only one, and the other three each move the caching logic to a different place, with real consequences for who's responsible for keeping the cache correct.
3 min read
In cache-aside (already covered in depth in System Design), the application code itself checks the cache, and on a miss, fetches from the database and populates the cache manually. The cache is a passive store; all the logic for when to read and write it lives in application code. This lesson's job is to place that pattern alongside the three others real systems use, so the choice between them is deliberate rather than "cache-aside by default because it's the one most examples show."
A read-through cache sits in front of the database as a library or proxy that the application always calls — on a miss, the cache itself (not the application's own code) knows how to fetch from the underlying source and populate itself before returning the result. The application code is simpler (cache.get(key), never a manual "check cache, then fall back to database" branch), but it requires the cache layer to be configured with knowledge of how to load the specific data — a real setup cost cache-aside avoids by keeping that logic in ordinary application code.
Write-through writes to the cache, which immediately and synchronously writes the same value to the underlying database before the write call returns. The cache is always in sync with the database the moment a write completes — there's no window where the cache is stale after a write, unlike cache-aside's invalidate-on-write approach, which only works if every write path remembers to invalidate. The cost is write latency: every write now waits on both the cache update and the database write, not just the database alone.
Write-behind writes to the cache immediately and returns, then asynchronously flushes the change to the database sometime after — the opposite latency trade-off from write-through. Writes are fast (only the cache write is on the critical path), but there's a real window where the database doesn't yet reflect a change the cache already has, and a cache failure before the flush completes means the write is lost entirely unless the cache itself persists durably. This pattern fits workloads with heavy write volume where some risk of loss on a cache crash is acceptable — it's a real trade-off, not a strictly worse version of write-through.
| Pattern | Who does the cache-population work | Write latency | Staleness risk |
|---|---|---|---|
| Cache-aside | Application code, explicitly | Normal (cache not involved in writes) | Real, until explicit invalidation or TTL |
| Read-through | The cache layer itself | Normal | Same as cache-aside, handled by the library instead of by hand |
| Write-through | The cache layer itself | Higher (waits on both writes) | None — cache and database stay in sync |
| Write-behind | The cache layer itself | Lowest | Real — a window where the database lags, plus data-loss risk on a cache crash |
Most real systems default to cache-aside for its simplicity and because most libraries already provide it, and reach for write-through or write-behind specifically when the write-latency or staleness trade-off they each offer actually matters for a given workload — not as a strictly superior upgrade.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. In read-through caching, who is responsible for fetching from the source on a cache miss?
2. What does write-through guarantee that cache-aside's invalidate-on-write approach doesn't?
3. What real risk does write-behind (write-back) introduce?
Caching Strategies