Caching Strategies Summary
Updated June 8, 2026Every caching pattern answers the same two questions: when does data get into the cache? and when does the cache reflect a change? The five canonical strategies are Cache-Aside, Read-Through, Write-Through, Write-Behind, and Refresh-Ahead. Each answers those questions differently. Picking the right one depends entirely on your read/write ratio, consistency requirements, and tolerance for complexity.
1. Cache-Aside (Lazy Loading)
Cache-Aside is the most common pattern. The application is in full control. It explicitly checks the cache, falls back to the database on a miss, and populates the cache manually.
Flow:
- Read from cache
- Hit: return cached data
- Miss: query database, store result in cache, return data
Pseudocode:
function getProduct(id) {
const cached = cache.get(`product:${id}`);
if (cached) return cached;
const product = db.find(id);
cache.set(`product:${id}`, product, TTL);
return product;
}Pros:
- The cache only contains data that's actually been requested — no wasted memory
- Cache failures are non-fatal; the app can always fall back to the database
- Works with any database and any cache
Cons:
- Cache misses are slow (you pay the full round-trip to the DB)
- The application code is responsible for cache management — easy to forget or get wrong
- Risk of stale data if you forget to invalidate on writes
Best for: Read-heavy applications where cache misses are tolerable and you want maximum flexibility. This is the default choice for most teams.
2. Read-Through
The cache sits in front of the database and manages its own population. On a miss, the cache fetches from the database and stores the result before returning it. The application only ever talks to the cache.
Pros:
- Application code is simpler — no cache management logic
- Good at coalescing concurrent misses for the same key
Cons:
- First request for any key is always slow
- Less flexibility in how data is fetched and transformed
Best for: When you want to keep cache logic out of your business layer. Common in frameworks with built-in caching middleware.
3. Write-Through
Every write goes to both the cache and the database synchronously. Cache is always in sync with the database.
Flow:
- Application writes a value
- Cache is updated
- Database is updated
- Write returns
Pros:
- Cache is always fresh — no stale reads after writes
- No need for TTL-based invalidation on recently-written data
Cons:
- Write latency increases (two synchronous writes)
- Cache fills with data that may never be read
Best for: Workloads where reads and writes are balanced and data freshness is critical.
4. Write-Behind (Write-Back)
Write-behind is the async cousin of write-through. Writes go to the cache immediately and are batched and asynchronously flushed to the database in the background.
Flow:
- Application writes a value
- Cache is updated immediately — write returns fast
- Cache queues the write
- Background process flushes dirty keys to the database
Write-behind fast path: the app writes only to Redis and returns immediately. The database is not touched during the write.
Flush phase: a background worker batches dirty keys from Redis into a single database query every 10 seconds.
Pros:
- Dramatically lower write latency — the application doesn't wait for the database
- Reduces database write pressure through batching — great for write-heavy workloads
- Natural write coalescing: if the same key is updated 100 times in a second, the database may only see 1 write
Cons:
- Data loss risk — if the cache crashes before flushing, you lose the queued writes
- More complex to implement reliably — you need durability guarantees on the queue
- Harder to reason about consistency
Best for: Very write-heavy workloads where write latency is a bottleneck and you can tolerate some data loss risk. Think analytics counters, like counts, view counts, real-time activity feeds.
Write-behind is essentially what most databases do internally with their write-ahead log and buffer pool. The difference is that databases do it with strong durability guarantees.
5. Refresh-Ahead
Refresh-Ahead is a proactive strategy. The cache predicts which keys will expire soon and refreshes them in the background before they expire. The goal is to eliminate cache misses entirely.
Flow:
- A key is set with a TTL
- As the TTL approaches expiry (e.g., when 80% of TTL has elapsed), a background job fetches fresh data and updates the cache
- The key never actually expires — it's refreshed before it can
Pros:
- Near-zero cache miss latency — keys are refreshed before users can miss them
- Predictable performance — no cold-miss spikes
Cons:
- You need to know in advance which keys are "hot" and worth refreshing
- Wastes resources refreshing keys that might not be requested again
- More complex infrastructure (needs a background refresh mechanism)
Best for: Highly predictable hot data: homepage content, top 10 leaderboards, featured product lists. Anything that's fetched constantly and where a cache miss would be very painful.
Decision Matrix
| Scenario | Recommended Strategy |
|---|---|
| Read-heavy, writes are rare | Cache-Aside or Read-Through |
| Reads and writes are balanced, freshness critical | Write-Through + Read-Through |
| Extremely write-heavy, some data loss OK | Write-Behind |
| Hot keys that must never miss | Refresh-Ahead |
| Simple setup, full control | Cache-Aside |
| Want cache to manage itself | Read-Through |
Combining Strategies
Real systems often combine patterns. A common production setup:
- Read-Through for the read path (cache manages population)
- Write-Through for the write path (cache stays fresh)
- Write-Behind for high-volume counters (likes, views) where batching to DB is acceptable
- Refresh-Ahead for homepage data and featured content that must always be fast
Different data has different freshness and performance requirements. Don't feel pressure to pick one strategy across your entire system.
A Note on Thundering Herd
A cache miss on a popular key can trigger a "thundering herd": hundreds of simultaneous requests all hitting the database at once. Solutions:
- Mutex/lock on miss: Only one thread fetches from the DB; others wait
- Probabilistic early expiration: Refresh a key slightly before it expires with some probability, so the refresh is spread out
- Refresh-Ahead: Refresh before expiry, so misses never happen
Summary
The five caching strategies differ in where logic lives and how consistent the cache stays with the database:
- Cache-Aside: App manages the cache. Flexible, most common.
- Read-Through: Cache manages reads. Simpler app code.
- Write-Through: Writes go to both cache and DB. Always fresh.
- Write-Behind: Writes go to cache first, DB later. Fastest writes, some risk.
- Refresh-Ahead: Cache refreshes itself before TTL. Zero misses on hot keys.
Choose based on your read/write ratio, consistency tolerance, and how much complexity you're willing to own.
How helpful was this content?
Comments
Sign in to join the discussion
Saved on this device only
Sign in to sync progress across devices