Caching Strategies you should know
"Add a cache" is the most common answer in a system design interview, and the most commonly hand-waved. Caching is not one thing - it is a set of read strategies and write strategies, and picking the wrong pair for your workload gives you stale data, slow writes, or lost writes instead of speed. This post walks the five strategies you actually need, then answers the question that matters: which one fits a read-heavy, a write-heavy, and a balanced workload.
Caching is one of the core system design building blocks; here we go one level deeper into how the cache and the database actually talk.
Two questions define every strategy
Strip away the names and every caching strategy is answering just two questions:
- On a cache miss, who loads the data from the database - the application, or the cache itself? That is your read strategy.
- On a write, what gets updated first (cache, database, or both), and when do you tell the caller the write is done? That is your write strategy.
Read and write strategies are chosen separately and then combined. So "cache-aside" (a read strategy) is usually paired with "write-around" or "write-through" (write strategies). Keep the two axes separate in your head and the whole space gets simple.
The read strategies
Cache-aside (lazy loading). The application owns the cache. On a read it checks the cache; on a hit it returns, on a miss it reads the database, writes the value into the cache, and returns it. Only data that is actually requested ever gets cached. It is the general-purpose default: simple, and resilient to cache failure - if the cache dies, the app just talks to the database directly (slower, but alive). Its weaknesses are that the first read of any key is always a miss, and the app code carries the cache logic. Redis and Memcached are the usual stores. (Redis, Memcached.)
Read-through. Same read behavior, but the cache sits inline and loads from the database itself on a miss - the application just asks the cache and never talks to the database directly for reads. The logic moves out of your app and into the cache layer (a library or a managed cache). Cleaner application code, but you depend on the cache provider, and the first read of a key is still a miss (people "warm" the cache to avoid the cold-start penalty).
The two are close cousins - the difference is who fetches on a miss (your app vs the cache). Both shine on read-heavy data.
The write strategies
Write-through. Every write goes to the cache and the database synchronously, before the write is acknowledged. The cache is always in sync with the database, so a read right after a write is correct - no staleness. The price is write latency: every write pays for two hops. Good when correctness right after a write matters.
Write-back (write-behind). The write goes to the cache and is acknowledged immediately; the cache flushes to the database asynchronously, often batched. Writes are very fast and the database is shielded from write bursts. The risk is real: if the cache dies before it flushes, those writes are gone - so it is for high-volume paths that can tolerate some loss or where you have a durable write buffer. It also introduces eventual consistency between cache and database.
Write-around. The write goes straight to the database and skips the cache; the cache is filled only later, on a read miss (usually via cache-aside). This keeps data that is written but rarely read soon out of the cache, so you do not pollute it with cold entries. The catch: the first read after a write is a miss, and if the key was already cached, that cached copy is now stale until it expires or you delete it.
Which strategy for which workload
This is the decision you are really being asked to make. Match the strategy to the shape of your traffic.
Read-heavy
Reads dominate (a timeline, a product page, a URL shortener's redirects), the same keys are requested repeatedly, and data changes rarely. Use cache-aside or read-through for the reads. The hot set lives in memory, most requests never touch the database, and latency drops hard. If the data is effectively immutable once written, caching is almost free - there is nothing to invalidate. For very hot, predictable keys, refresh-ahead (proactively refreshing an entry before it expires) hides even the occasional miss. Pair the reads with write-through if readers must see writes immediately, or write-around if freshly written data is not read right away.
Write-heavy
Writes dominate or arrive in bursts (event ingestion, logging, metrics, bulk imports). Here caching the write path can hurt more than help:
- Write-around when written data is rarely read soon after - the common case for logs and append-only streams. Writes stay simple (straight to the database) and you do not fill the cache with entries nobody reads.
- Write-back when you need the writes themselves to be fast and want to absorb bursts, and you can tolerate eventual consistency and some loss risk. It batches writes and shields the database, but reserve it for narrow, high-volume, low-criticality paths - not your whole system.
Balanced (mixed reads and writes, read-your-writes)
Reads and writes are both significant and users expect to see their own writes immediately (a profile edit, a comment, a cart). Write-through paired with read-through (or cache-aside) is the sweet spot: writes keep the cache and database in lockstep so reads are always fresh, at the cost of slightly slower writes. For mixed workloads that are also high-volume, read-through + write-back trades that freshness for write speed - only if eventual consistency is acceptable.
Here is the whole map in one table:
| Strategy | Reads | Writes | Consistency | Main risk | Best workload |
|---|---|---|---|---|---|
| Cache-aside | App loads on miss | (pair a write strategy) | Depends on write strategy | First read is a miss | Read-heavy (default) |
| Read-through | Cache loads on miss | (pair a write strategy) | Depends on write strategy | Cold-start miss | Read-heavy |
| Write-through | - | Cache + DB, synchronous | Strong (cache == DB) | Slower writes | Balanced / read-your-writes |
| Write-back | - | Cache now, DB async | Eventual | Data loss if cache fails | Write-heavy, burst absorption |
| Write-around | - | DB only, skip cache | Cache can be stale | First read after write is a miss | Write-heavy, rarely-read data |
The most common production combinations: cache-aside + write-around (read-heavy with writes that are not read back immediately), read-through + write-through (balanced, consistency-first), and read-through + write-back (high write volume, speed over freshness).
The parts people forget
- Invalidation is the hard part. Any strategy where the database can change without the cache knowing (write-around, or an out-of-band update) leaves stale entries. The fixes are a short TTL (bound how long staleness can last) and explicit deletion of the key on write. Immutable data sidesteps this entirely, which is why it is worth noticing when your data never changes.
- Cache stampede. When a hot key expires, thousands of requests miss at once and hammer the database to recompute it. Mitigate with a lock so only one request recomputes, or staggered TTLs.
- Cold cache. After a restart the cache is empty and every request is a miss - which can crush the database. Warm the critical keys before taking traffic.
- Eviction is a separate axis. LRU, LFU, and TTL decide what to drop when the cache is full; that is orthogonal to the read/write strategy and you choose it too.
- This is the read side of CQRS. Serving reads from a fast, denormalized cache while writes go to the source of truth is exactly the separation CQRS formalizes.
Picking one in practice
Start from your traffic, not the strategy. Read-heavy and mostly-immutable? Cache-aside, and you are done. Read-heavy but users must see their writes? Add write-through. Write-heavy with data that is not read back soon? Write-around, and keep the cache for the reads that do happen. Write-heavy and speed-critical, loss-tolerant? Write-back on that path only. There is no single best cache - each strategy is a different trade of speed, freshness, and risk, and naming which one you are choosing and why is exactly the reasoning an interview is looking for.

