Caching Strategies โ Revision Notes
Caching stores frequently accessed data in fast memory to cut latency and offload the database. It is the single highest-leverage performance tool in system design โ and interviewers expect you to know where to cache, which strategy, and how to handle staleness.
Where and how
| Layer | Example |
|---|---|
| Client / browser | Cache static assets, API responses |
| CDN | Cache static content near users |
| Application | In-memory (local) cache |
| Distributed | Redis / Memcached shared across servers |
Write strategies: write-through (write cache+DB together, consistent, slower), write-back (write cache, async to DB, fast, risk of loss), cache-aside (app loads on miss โ most common).
Eviction: LRU is the default; also LFU, TTL-based expiry.
Exam Tricks & Tips
- ๐ฏ Cache-aside (lazy loading) is the default โ app checks cache, on miss reads DB and populates.
- ๐ฏ Use Redis for a shared/distributed cache across horizontally scaled servers.
- ๐ฏ LRU eviction is the standard answer when the cache fills up.
- ๐ฏ Always address staleness โ set a TTL, or invalidate on write, to avoid serving old data.
- ๐ฏ Put a CDN in front of static content to cut latency for global users.
- โ Common mistake: caching without an invalidation/TTL plan โ "the two hard things are naming and cache invalidation."
Expected pattern: Comes up whenever reads dominate; interviewers probe write strategy and how you handle stale data.
Quick recap: Cache hot data in Redis/CDN, default to cache-aside + LRU, and always have a TTL/invalidation plan for staleness.
Caching Strategies โ Flashcards (Interview Prep)
Cover the answer, recall, then check. 11 cards on caching.
Q1. What does caching achieve?
A1. Stores hot data in fast memory to cut latency and offload the database.
Q2. Default caching strategy?
A2. Cache-aside (lazy loading): app checks cache, on miss reads DB and populates it.
Q3. Write-through vs write-back?
A3. Write-through writes cache+DB together (consistent, slower); write-back writes cache then async to DB (fast, risk of loss).
Q4. Default eviction policy?
A4. LRU (Least Recently Used).
Q5. What is used for a distributed/shared cache?
A5. Redis or Memcached, shared across horizontally scaled servers.
Q6. What does a CDN cache?
A6. Static content served from edge locations near users.
Q7. Why must you plan for staleness?
A7. To avoid serving old data โ use a TTL or invalidate on write.
Q8. Common caching mistake?
A8. Caching with no invalidation/TTL plan (cache invalidation is famously hard).
Q9. Where can caching happen (layers)?
A9. Client/browser, CDN, application (in-memory), and a distributed cache.
Q10. When is caching most valuable?
A10. When reads dominate writes (read-heavy workloads).
Q11. What does a cache "miss" trigger in cache-aside?
A11. A read from the database, then populating the cache for next time.
Caching Strategies
Caching is the highest-leverage performance lever in system design: storing frequently-accessed data in fast memory can cut latency by orders of magnitude and take huge load off your database. Almost every design answer improves when you add a cache โ but only if you can also discuss invalidation and eviction, because a cache that serves stale data is worse than no cache.
What this covers / why it matters: where caching sits, the main patterns, eviction policies, and the hard part (invalidation). It's one of the most reliably-asked and high-impact system-design topics.
The approach
Beginner โ what and where to cache
A cache is a fast store (usually in-memory, like Redis or Memcached) holding copies of data that's expensive to fetch or compute, so repeated reads are near-instant. Caches live at many layers: browser, CDN (for static assets near the user), application-level (Redis in front of the DB), and inside the database itself. The classic win is putting a Redis cache between your app and a slow database for hot read queries.
Intermediate โ the read/write patterns
- Cache-aside (lazy loading): the app checks the cache; on a miss it reads the DB, stores the result in the cache, and returns it. The most common pattern โ the cache only holds what's actually requested.
- Write-through: writes go to the cache and the DB together โ the cache is always fresh, at the cost of slower writes.
- Write-back: writes go to the cache first and to the DB asynchronously โ fast writes, but risk of data loss if the cache fails before flushing.
Advanced โ eviction and the hard part, invalidation
A cache has limited memory, so it needs an eviction policy โ usually LRU (evict the least-recently-used entry), sometimes LFU or TTL-based expiry. The genuinely hard part is invalidation: when the underlying data changes, stale cache entries must be removed or updated, or users see old data. Strategies include a TTL (entries expire after N seconds โ simple, slightly stale) and explicit invalidation (delete/update the cache entry on write โ fresh, but you must not miss any write path). Say the famous line and mean it: cache invalidation is one of the genuinely hard problems.
Worked example
"Reads on your product page are hammering the database. How do you speed it up?"
"I'd add a Redis cache in front of the database using the cache-aside pattern. On a product-page request, the app first checks Redis for that product; on a hit it returns instantly, on a miss it reads the database, stores the result in Redis with a TTL, and returns it. That offloads the vast majority of reads from the DB, since product data is read far more than it's written. For freshness I'd combine two things: a modest TTL (say 5 minutes) as a safety net, plus explicit invalidation โ whenever a product is updated, the write path also deletes that product's cache key, so the next read repopulates fresh data. For memory, Redis would use LRU eviction so the hottest products stay cached. The trade-off is a small window of staleness bounded by the TTL, which is fine for product listings."
Pattern, freshness via TTL + explicit invalidation, eviction, and the stated trade-off โ a complete caching answer.
What interviewers look for
- You add a cache for read-heavy hot data.
- A named pattern (usually cache-aside) with the read/write flow.
- Invalidation strategy โ TTL and/or explicit โ not just "add a cache".
- Eviction awareness (LRU) and the staleness trade-off.
Do's and don'ts
- Do describe the cache-aside flow (check cache โ miss โ DB โ populate).
- Do address invalidation and eviction explicitly.
- Don't add a cache and ignore how it stays fresh.
- Don't cache data that changes constantly or must be perfectly consistent.
- Mnemonic: CHIME โ Cache-aside by default, Hit/miss flow, Invalidate (TTL + explicit), Manage Eviction (LRU).
Adding a cache but never addressing invalidation. A cache that serves stale data after the source changes causes bugs that are worse and harder to trace than a slow-but-correct system. Always pair a cache with a freshness strategy (TTL and/or explicit invalidation on write).
- โ- Cache hot, expensive-to-fetch read data in fast memory (Redis) for huge latency/load wins.
- โ- Cache-aside is the default: check cache, miss โ read DB โ populate โ return.
- โ- Keep it fresh with TTL and/or explicit invalidation on writes.
- โ- Bound memory with an eviction policy (LRU); accept a small staleness trade-off.
- โCaching is the top performance lever, but only if it stays correct. Use cache-aside for hot reads, keep entries fresh with TTL plus explicit invalidation, evict with LRU, and always state the staleness trade-off.
Caching Strategies โ Worked Example
Worked Example
Problem/Question: "Reads on your product-details page are hammering the database. How would you use caching, and what are the pitfalls?"
Solution/Model answer:
Approach: put a cache (e.g., Redis) in front of the database for hot, read-heavy, relatively static data like product details.
Caching patterns:
- Cache-aside (lazy loading): app checks cache first; on a miss, reads the DB and populates the cache. Simple and common.
- Read-through / write-through: the cache layer handles loading/writing; write-through keeps cache and DB in sync on writes (higher write latency).
- Write-back: write to cache first, flush to DB later (fast writes, risk of data loss).
Key concerns: - Invalidation ("the hardest problem"): set a TTL and/or invalidate on update, so stale prices aren't shown.
- Eviction policy: LRU is a sensible default when memory fills.
- Thundering herd / cache stampede: many misses at once hammer the DB โ mitigate with request coalescing or staggered TTLs.
Result: "For product details I'd use cache-aside with a short TTL plus explicit invalidation on price/stock changes, and LRU eviction."
Answer/Takeaway: Cache hot read-heavy data (cache-aside is the default), and treat invalidation as the core challenge โ use TTLs plus event-based invalidation to avoid stale data, LRU eviction when full, and guard against cache stampedes.
- โ- Cache-aside is the go-to pattern; write-through/write-back trade write latency vs durability.
- โ- Invalidation is the hard part โ combine TTLs with update-triggered invalidation to bound staleness.
- โ- Plan for eviction (LRU) and cache stampedes (request coalescing, jittered TTLs).