Why you need a cache, and when to use it
Reading from a database or another service is slow compared with reading from memory. If thousands of users ask for the same thing, the slow source repeats the same work again and again. A cache keeps a copy of data in a faster place, so most requests are answered without touching the source.
It gives you two things interviewers always ask about:
- Lower latency: a hit is answered in a moment instead of waiting for the database.
- Less load on the source: every hit is one read the database never sees, so it can serve more users or be smaller.
Use a cache when:
- Data is read far more often than it changes: product pages, profiles, configuration.
- The same data is asked for repeatedly (a few keys are popular).
- A result is expensive to compute and can be reused.
- A little staleness is acceptable, for example a view count that is a minute old.
Skip it, or use something else, when:
- Every request asks for different data (no repeats): a cache will mostly miss. The demo's "scanning" traffic shows this.
- Data must always be exactly current, such as an account balance at the moment of payment: read the source.
- The query is just slow: add an index or fix the query first (see the Databases lesson).
- The data is large and rarely read: caching it wastes memory.
Try it
Pick an eviction rule and a kind of traffic, then step through the 20 requests. The cache holds only four items. Watch the green and red dots, and the hit rate.
Cache hits, misses and eviction
Step 1 of 21 · Press Space to play, arrow keys to step-
- empty
- empty
- empty
- empty
slow
Green dot = hit, red dot = miss
Start: an empty cache with room for 4 items, in front of a database. A hit is answered from the cache in about 2 ms. A miss goes to the database and takes about 50 ms. Those times are illustrative.
- requests:
- 0
- hits:
- 0
- misses:
- 0
- hit rate %:
- 0
- database reads:
- 0
- average latency (ms):
- 0
1 request = one step. Latencies are illustrative: a hit is about 2 ms and a miss about 50 ms.
Things to try:
- A few popular keys with LRU: a 65% hit rate, only 7 database reads out of 20. Average latency falls from 50 ms to about 19 ms.
- Scanning many keys once: 0% hits with every rule. A cache cannot help when keys do not repeat, and it just adds work.
- Hot keys plus one-offs: LRU and LFU keep the hot keys A and B and reach 60%. FIFO throws out A and B because they arrived first, and drops to 40%.
- Tick Flush the cache before request 11. This is a cache restart or crash. Hits drop and database reads rise from 7 to 9. In a real system with millions of requests, that surge can overload the database.
Caching strategies
These are the patterns for reading and writing through a cache.
| Strategy | How it works | Good when | Weakness |
|---|---|---|---|
| Cache-aside (lazy loading) | The app checks the cache. On a miss it reads the database and stores the result in the cache | Read-heavy data, the most common pattern | First read is slow, and the app must keep the cache correct |
| Read-through | The cache itself loads from the database on a miss; the app only talks to the cache | You want simpler app code | The cache layer needs to know how to load; same cold start |
| Write-through | Every write goes to the cache and the database together, before it is acknowledged | Data read soon after it is written, and you want the cache always fresh | Slower writes; you cache items that may never be read |
| Write-behind (write-back) | Write to the cache now, and save to the database later in the background | Write-heavy workloads where fast writes matter | Data can be lost if the cache fails before it is saved |
| Write-around | Write to the database only; the cache fills on the next read | Data that is written once and rarely read | The first read after a write is a miss |
Local cache vs shared cache
- In-process (local) cache: lives inside each app server's memory. It is the fastest, with no network call, but each server holds its own copy, copies drift apart, and a restart empties it.
- Shared (distributed) cache: a separate service such as Redis or Memcached that all servers use. One copy for everyone and it survives an app restart, but each lookup is a network call and it is one more thing to run.
Many systems use both: a small local cache for the hottest items, in front of a shared cache, in front of the database. A CDN (content delivery network) is the same idea at the edge: copies of pages and files cached in locations near users.
How cache eviction and invalidation work
A cache is small, so something has to go when it fills up. And a copy can become wrong when the source changes. Two separate rules handle those problems.
Eviction decides what to remove when the cache is full:
- LRU (least recently used): remove the item not touched for the longest time. A good default.
- LFU (least frequently used): remove the item used the fewest times. Good when some items are popular for a long time.
- FIFO (first in, first out): remove the oldest item, whether or not it is still popular. Simple, but it can drop hot items.
Invalidation keeps copies from going stale:
- Expiry (TTL): every entry gets a time to live, for example five minutes. After that it is dropped, and the next read fetches fresh data. Simple, and it bounds how stale data can be.
- Delete on write: when the source changes, delete the cached copy (or update it). The next read reloads it.
- Versioned keys: put a version in the key, such as
user:42:v7. A change creates a new key, and the old one expires.
Cache stampede happens when a popular entry expires and many requests miss at once, so they all hit the database together. Fixes: only let one request reload the entry while the others wait, add a little random jitter to TTLs so entries do not expire together, or refresh popular entries before they expire.
Caching vs the alternatives
| Concern | Cache | Faster query or index | Read replicas | Precomputed results |
|---|---|---|---|---|
| Lower read latency | Yes, the biggest gain | Some | Some | Yes |
| Less load on the database | Yes | Some | Yes | Yes |
| Data always current | No, it can be stale | Yes | Almost, replicas can lag | No, as fresh as the last run |
| Extra moving part | Yes | No | Yes | Yes (a job) |
| Needs app changes | Yes | No | Some | Some |
| Best for | Repeated reads of the same data | Slow queries | Spreading read traffic | Expensive reports and feeds |
A cache is the right first step for repeated reads. If one query is slow for everyone, fix the query. If you need current data on many reads, replicas are safer.
Avoiding a new single point of failure
A cache is meant to be optional, but systems can come to depend on it. If the cache empties, every request goes to the database at once, which can take it down. Protect against that:
- Treat the cache as disposable. The database must be able to survive a cold cache, or you need a plan such as rate limiting or serving stale data.
- Run the cache redundantly (replicas or a cluster) so one node failing does not empty everything.
- Warm it up before sending full traffic after a restart.
- Fail open: if the cache is down, read from the database, and protect the database with limits.
The trade-off: you gain speed and lower load, and accept stale data and one more system whose failure you must plan for.
Common mistakes
- No expiry. Entries live forever and go stale. Always set a TTL, even a long one.
- Caching data that does not repeat. It wastes memory and adds latency. Check the hit rate before you celebrate.
- Forgetting to invalidate on write. Users see old data after an update. Delete or update the entry when the source changes.
- Caching user-specific data under a shared key. One user can see another's data. Include the user in the key, or do not cache it.
- Ignoring stampedes. A hot key expiring can flood the database. Use locking, jitter or early refresh.
- Treating the cache as the source of truth. It can be emptied at any time. The database stays authoritative.
How to talk about caching in an interview
- Say what you cache and why: "product pages are read much more than written, so cache them."
- Pick a pattern: "cache-aside with a five-minute TTL, delete on write", and say what you give up.
- Say where it sits: local, shared, CDN, or several layers.
- Mention unprompted: eviction (LRU), stampede, what happens on a cold cache, and how you will measure the hit rate.
- Name the trade-off: speed and lower load in return for staleness and complexity.