Ever wondered how applications like Instagram and Twitter(X) deliver content almost instantly? Caching makes this possible by reducing repeated database and network calls. However, cached data can quickly become outdated, and managing this staleness is one of the hardest problems in system design. Cache invalidation is the mechanism that solves it.
Cache invalidation answers one core question:
“When cached data becomes wrong, how do we detect it and fix it?”
Eviction removes data because of memory pressure.
Invalidation removes data because of data correctness.
Why cache invalidation is hard?
A cache creates two copies of data:
- One in cache
- One in the source of truth (DB)
The moment DB changes, cache risks becoming stale.
So every invalidation strategy is trying to balance:
- Freshness
- Performance
- Complexity
This is why people say:
“There are only two hard things in computer science: naming things, cache invalidation, and off-by-one errors.”
1. Time-based invalidation (TTL)
This is the simplest and most common approach.
Idea
Cached data is valid only for a fixed duration.
After that:
- Treat it as expired
- Fetch fresh data
Characteristics
- Simple
- No coordination needed
- Accepts staleness
We already implemented this here.
Key insight:
- TTL is not eviction
- TTL is invalidation triggered by time
When to use
- Analytics
- Feeds
- Product listings
- Weather
- Any data that “eventually updates”
2. Write-based invalidation (invalidate on write)
This is the most important strategy in real systems.
Idea
Whenever data is updated in DB:
- Invalidate corresponding cache entry
Flow
Update DB
→ Delete cache entry
→ Next read repopulates cache
This is usually paired with cache-aside.
JavaScript example
Assume:
- DB update happens
- Cache key is
user:{id}
function updateUser(id, newData) {
updateUserInDB(id, newData);
// Invalidate cache
cache.delete(`user:${id}`);
}
Next read:
- Cache miss
- Fresh DB value is cached
Characteristics
- Strong consistency (after write)
- Simple logic
- Slightly higher read latency after writes
When to use
- User profiles
- Settings
- Config data
3. Update-on-write (write-through style invalidation)
Instead of deleting cache, you update it immediately.
Flow
Update DB
→ Update cache
Example
function updateUser(id, newData) {
updateUserInDB(id, newData);
cache.set(`user:${id}`, newData);
}
Characteristics
- No cache miss after write
- Cache always fresh
- Slightly more complex
Risk
- If DB write succeeds but cache update fails → inconsistency
When to use
- Read-heavy systems
- Low tolerance for stale reads
4. Version-based invalidation
Instead of deleting data, you detect staleness.
Idea
Attach a version or timestamp to cached data.
Example cache entry
{
value: user,
version: 5
}
On read:
- Compare version with DB
- If mismatch → refresh cache
Example (simplified)
function getUser(id) {
const cached = cache.get(id);
const dbVersion = getUserVersionFromDB(id);
if (cached && cached.version === dbVersion) {
return cached.value;
}
const fresh = fetchUserFromDB(id);
cache.set(id, { value: fresh, version: dbVersion });
return fresh;
}
Characteristics
- Very safe
- More DB calls
- Used in high-consistency systems
5. Event-based invalidation (distributed systems)
Used when:
- Multiple services
- Multiple caches
Idea
DB change emits an event:
- All caches listening invalidate their entries
Example
UserUpdatedEvent(userId)
→ All services delete user:{id}
Technologies
- Kafka
- Pub/Sub
- Redis streams
Characteristics
- Scales well
- Complex infra
- Eventual consistency
6. Manual / administrative invalidation
Sometimes you just need a big red button.
Examples:
- Price bug
- Bad deployment
- Emergency rollback
cache.clear();
Simple but powerful.
Cache stampede
Problem
TTL expires → 1000 requests → all hit DB at once.
Common mitigations
- Lock per key
- Request coalescing
- Refresh-ahead
- Jittered TTL
Example (conceptual):
if (isFetching(key)) {
waitForResult(key);
} else {
fetchAndPopulate(key);
}
Let me know in comments, if you would like to see implementation of this.
Mental model (this is key)
| Concept | Solves |
|---|---|
| TTL | Staleness over time |
| Invalidate-on-write | Staleness on updates |
| Update-on-write | Cache freshness |
| Versioning | Consistency correctness |
| Eviction (LRU) | Memory pressure |
Cache invalidation focuses on keeping data correct, but correctness alone isn’t enough in real systems. Memory is limited, and caches cannot grow indefinitely. Even perfectly valid data must eventually be removed to make room for newer or more frequently used entries. This is where eviction policies come into play. In the next section, we’ll look at how caches decide what to keep and what to discard under memory pressure, and why these decisions have a direct impact on performance and scalability. This blog is part of Caching 101 to Advanced series.