Caching
Learn the standard caching strategies — where the cache sits, when it's written, and how stale data gets cleaned up — so you can justify a caching layer instead of just naming Redis.
Problem it solves
Databases are slow relative to how often the same data is re-read. A cache trades a small amount of staleness risk for a large reduction in read latency and database load — but only if you're explicit about who writes to the cache, when, and how invalidation happens. Naming "add a cache" without that detail doesn't answer the interviewer's actual question.
When not to use it
- Write-heavy, read-rarely data — the cache would be evicted before it's ever read again, wasting memory.
- Strongly consistent data where even brief staleness is unacceptable (e.g. an account balance mid-transaction) unless paired with careful invalidation.
- Data with no locality/skew — if every read is for a different key, a cache's hit rate stays near zero.
Failure modes to call out
- Cache stampede: a hot key expires and thousands of concurrent requests all miss and hit the DB at once — mitigate with request coalescing or jittered TTLs.
- Stale reads after a write, when invalidation is forgotten on one code path but not another.
- Unbounded cache growth without an eviction policy (LRU/LFU), leading to memory pressure.
- Caching per-user data behind a shared cache key, leaking one user's data to another.
Alternatives & trade-offs
| Alternative | Trade-off |
|---|---|
| Cache-aside (lazy load) | App checks cache, falls back to DB and populates cache on a miss. Simple and cache stays populated with only what's actually read, but the first read after eviction is slow and a stampede can hit the DB hard. |
| Write-through | Every write goes to the cache and the DB together, so reads through that same path stay fresh under normal operation. It doesn't guarantee zero staleness — a partial write failure, a concurrent writer that bypasses the cache, or a replica reading stale data can still show old values — and every write pays the cache-write cost, with an unread key still occupying cache space. |
| Write-back (write-behind) | Writes hit the cache and are flushed to the DB asynchronously. Fastest writes, but risks data loss if the cache fails before the flush. |
| TTL-based expiration | Simplest invalidation — no explicit invalidation code, but you're trading a bounded staleness window for that simplicity. |
Diagram
Cache-aside read path: check the cache first, only fall back to the database on a miss.
Scroll the diagrams sideways to see all of them.
Estimation example
A product page read 5,000 times/sec but updated only once every few minutes is an extreme caching win: cache-aside with a short TTL (a few seconds) turns nearly all of that traffic into cache hits, and even a brief staleness window is invisible to users, cutting database read load by roughly two orders of magnitude.
Say this out loud in the interview
- "This access pattern is read-heavy with locality, so I'd add a cache-aside layer here — is that the right trade-off given the staleness you can tolerate?"
- "On a write, do we need to invalidate or update the cache, and on which code paths?"
- "What's our eviction policy, and how do we avoid a stampede when a hot key expires?"
Practice it in a case study
Design a URL Shortener
Redirect lookups are read-heavy and a natural fit for a cache-aside layer in front of the short-code → URL mapping.
Done with this lesson?
Mark it complete once you've worked through the content and practice above.
Next lesson
Queues & Streams