Cache Invalidation Patterns
Cache-Aside, Write-Through, and Write-Behind — three different answers to who updates the cache, and when.
Overview
Cache-Aside, Write-Through, and Write-Behind are the three standard patterns for keeping a cache and its underlying database in sync — each with a different answer to who's responsible for updating the cache, and when.
Why It Exists
A cache in front of a database only helps if it stays reasonably accurate. The real engineering problem in caching was never storing data fast — it's deciding exactly when and how a cache entry gets invalidated or refreshed as the underlying data changes. These three named patterns exist because 'just update the cache when data changes' hides a lot of real decisions about ordering, failure handling, and consistency.
Real World Example
A product page's price is cached. Under Cache-Aside, the app checks the cache first, and on a miss reads the database and writes the result into the cache for next time — simple, but a window of staleness follows every price update until it's re-cached. Under Write-Through, updating the price writes to the cache first, which synchronously writes through to the database — every read stays fresh, but every write pays the latency of both stores. Under Write-Behind, the write lands in the cache and is asynchronously flushed to the database later — the fastest writes, but a real risk of losing that update if the cache crashes before the flush happens.
The Three Patterns, One at a Time
Cache-Aside (Lazy Loading)
The application checks the cache first. On a miss, it reads the database and writes the result back into the cache for next time. Simple and the most common default, but every entry starts stale-or-missing until the first read repopulates it.
Write-Through
A write goes to the cache first, which synchronously writes it through to the database before the write is considered complete. Reads are always fresh, at the cost of every write paying the latency of both the cache and the database.
Write-Behind (Write-Back)
A write lands in the cache immediately and is asynchronously flushed to the database later. The fastest possible write path, but a real risk: if the cache crashes before the flush happens, that write is gone entirely.
Thundering Herd — the Shared Failure Mode
When a popular cache key expires, many concurrent requests can all miss it at the exact same instant and all hit the database simultaneously — a load spike at precisely the moment the cache was supposed to be protecting against one. This can happen under any of the three patterns above.
Diagram
Same data, three different orderings of cache vs. database
Cache-Aside
miss → read DB → write cache
Write-Through
write cache → sync write DB
Write-Behind
write cache → async flush DB later
Common Mistakes
Using Write-Behind for data where losing the most recent write is unacceptable
Why: If the cache crashes before flushing to the database, that write is gone entirely — tolerable for something like a view counter, unacceptable for a financial transaction.
Fix: Reserve Write-Behind for data that can tolerate occasional loss; use Write-Through or a database-first approach for anything that can't.
Not handling the thundering herd when a popular cache key expires
Why: Many concurrent requests can all miss the cache at the same instant and all hit the database simultaneously, spiking load right when the cache was supposed to be protecting it.
Fix: Use a locking or single-flight mechanism so only one request repopulates a given key while others wait, or stagger expirations with random jitter.
Mixing patterns inconsistently for the same piece of data
Why: If part of the codebase updates a key via Cache-Aside and another part via Write-Through, the two can race and leave stale or inconsistent cache state depending on which happens to run first.
Fix: Pick one pattern per data type or access path and apply it consistently everywhere that data is written.
Interview Questions
What's the difference between Cache-Aside and Write-Through?
Cache-Aside only touches the cache on a read miss — the application loads from the database and populates the cache itself. Write-Through updates the cache on every write, which synchronously writes through to the database, keeping the cache always fresh.
What's the specific risk of Write-Behind that the other two patterns don't have?
Write-Behind acknowledges a write as soon as it lands in the cache, before it's been persisted to the database. If the cache crashes before the asynchronous flush happens, that write is lost entirely — a risk Cache-Aside and Write-Through don't carry, since both eventually confirm the database write directly.
How would you prevent a thundering herd when a very popular cache key expires under high concurrent load?
The standard approach is a single-flight/locking mechanism: the first request that misses the cache acquires a lock and repopulates the key, while other concurrent requests for the same key either wait briefly for that result or serve a slightly stale value instead of all hitting the database independently. Staggering expirations with random jitter, so many keys don't expire at exactly the same moment, is a complementary mitigation for the same failure mode.
Production Best Practices
Do
✓Reserve Write-Behind for data that can tolerate occasional loss.
✓Protect hot cache keys with a single-flight/locking mechanism or jittered expirations.
✓Apply one consistent invalidation pattern per data type across the whole codebase.
Don't
✗Don't use Write-Behind for data where losing the latest write is unacceptable.
✗Don't leave a popular cache key's expiration unprotected against a thundering herd.
✗Don't mix invalidation patterns for the same key across different parts of the codebase.
Comparison
| Write Latency | Read Freshness | Data Loss Risk | |
|---|---|---|---|
| Cache-Aside | Fast (DB only) | Stale until next read-miss | None — DB is always source of truth |
| Write-Through | Slow (cache + DB) | Always fresh | None |
| Write-Behind | Fastest (cache only) | Always fresh | Possible — cache crash before flush |