Caching Patterns: Cache-Aside, Read-Through, Write-Through, and More
Master essential caching patterns including cache-aside, read-through, write-through, write-behind, and refresh-ahead. Learn when to use each pattern and common pitfalls.
Why Caching Is Essential
Caching is one of the most effective ways to improve system performance and reduce load on backend services. By storing frequently accessed data in fast memory, we can dramatically reduce latency and increase throughput.
The 90/10 rule: In many systems, 90% of the reads go to 10% of the data. Caching that hot 10% can yield massive performance gains.
However, caching introduces complexity: cache invalidation, consistency challenges, and the infamous "cache stampede." Understanding caching patterns helps you apply caching correctly.
Cache-Aside (Lazy Loading)
The most common caching pattern where the application code is responsible for loading data into the cache.
How It Works
- Application looks for data in the cache
- If found (cache hit), return it immediately
- If not found (cache miss), load data from the database
- Store the loaded data in the cache for future requests
- Return the data to the caller
Code Example
def get_user(user_id):
# Try cache first
user = cache.get(f"user:{user_id}")
if user is not None:
return user
# Cache miss - load from database
user = database.query("SELECT * FROM users WHERE id = ?", user_id)
# Populate cache for next time
cache.set(f"user:{user_id}", user, ttl=300) # 5 minute TTL
return user
Pros
- Simple to understand and implement
- Cache only contains data that's actually requested
- Natural integration with existing code
Cons
- Cache miss penalty includes both cache lookup and database query
- Application code must handle both cache and database logic
- Potential for cache stampede (many processes querying DB simultaneously on miss)
Mitigating stampede: Use techniques like locking, probabilistic early expiration, or request coalescing to prevent thousands of requests from hitting the database when a popular key expires.
Read-Through
In this pattern, the cache itself is responsible for loading data from the data store on a cache miss.
How It Works
- Application requests data from the cache
- If found (cache hit), return it immediately
- If not found (cache miss), the cache loads data from the database
- Cache returns the data to the application and stores it for future requests
Key Difference
With read-through, the application code only interacts with the cache. The cache provider handles the database interaction behind the scenes.
Pros
- Application code is simpler (only deals with cache)
- Centralized loading logic in the cache layer
- Reduces cache stampede risk (loading happens in cache layer)
Cons
- Requires a cache provider that supports read-through (Redis modules, custom implementations)
- Less control over loading logic for the application
- Still has cache miss penalty
Write-Through
Data is written to both the cache and the database synchronously.
How It Works
- Application writes data to the cache
- Cache synchronously writes data to the database
- Cache returns success to the application only after both writes succeed
Pros
- Strong consistency between cache and database
- Cache is always fresh (no stale reads for recently written data)
- Simple mental model: cache is always up-to-date
Cons
- Write latency includes both cache and database write times
- Higher write load on the database (every write goes to both)
- Can reduce write throughput significantly
Write amplification: Every write results in two writes (cache + DB). This can overwhelm your database if write-heavy.
Write-Behind (Write-Back)
Data is written to the cache immediately and asynchronously written to the database later.
How It Works
- Application writes data to the cache
- Cache returns success immediately
- Cache asynchronously writes data to the database in the background
- Application continues without waiting for database write
Pros
- Low write latency (only cache write time)
- Can batch multiple writes to the database for efficiency
- Absorbs write spikes (cache acts as a buffer)
Cons
- Risk of data loss if cache fails before writing to database
- Eventual consistency between cache and database
- More complex failure handling and recovery logic
Use case: Write-behind is great for workloads with high write tolerance for brief inconsistency, like analytics counters or social media likes.
Refresh-Ahead
Proactively refresh cache entries before they expire to avoid cache misses.
How It Works
- Cache tracks access patterns and predicts when entries will expire
- Before expiration, cache asynchronously loads fresh data from the database
- When expiration hits, cache serves the freshly loaded data (no miss)
- Application never experiences a cache miss for predictable access patterns
Pros
- Eliminates cache miss penalty for predictable traffic
- Maintains low latency even under load
- Reduces database load spikes
Cons
- Requires cache with intelligent prediction capabilities
- May waste resources refreshing data that won't be accessed
- More complex to implement correctly
Cache Invalidation Strategies
No discussion of caching is complete without addressing the hardest problem in computer science: cache invalidation.
Time-Based Expiration (TTL)
- Simplest approach: set a time-to-live on each cache entry
- Pros: Simple, predictable memory usage
- Cons: Can serve stale data until expiration; doesn't reflect data changes
Write-Based Invalidation
- Invalidate cache entries when data is updated in the database
- Pros: Keeps cache fresh; eliminates stale reads after writes
- Cons: Requires coordination between database writes and cache deletes
Pros
- Cache stays consistent with database
- Eliminates stale read window after writes
Cons
- More complex to implement (need to intercept database writes)
- Can cause cache stampede if many related entries are invalidated simultaneously
Pattern: Combine TTL as a safety net with write-based invalidation for freshness.
Choosing the Right Pattern
| Pattern | Best For | Consistency | Complexity |
|---|---|---|---|
| Cache-Aside | Read-heavy workloads, simple apps | Eventual | Low |
| Read-Through | Apps wanting simple cache interface | Eventual | Medium |
| Write-Through | Strong consistency requirements | Strong | Medium |
| Write-Behind | Write-heavy, tolerant of brief inconsistency | Eventual | High |
| Refresh-Ahead | Predictable access patterns, latency-sensitive | Eventual | High |
Common pitfalls:
- Caching everything (wastes memory, increases complexity)
- Ignoring cache invalidation (leads to stale data)
- Not handling cache stampede (overwhelms backend on miss)
- Overlooking memory eviction policies (unexpected cache misses)
What to Remember for Interviews
- Know the patterns: Be able to explain cache-aside, read-through, write-through, write-behind, and refresh-ahead.
- Trade-offs: Understand the consistency, complexity, and performance implications of each.
- Invalidation: Know why cache invalidation is hard and strategies to handle it.
- Stampede mitigation: Be familiar with techniques to prevent cache stampede.
- Real-world systems: Know how popular caches (Redis, Memcached) implement these patterns.
Practice: Design a caching layer for a social media feed. Which pattern would you use for user profiles? For the feed itself? For counting likes? Justify your choices.