Overview
A cache stores copies of frequently used data in a faster location so future requests avoid slow work. Reading from memory takes about 100 nanoseconds; a database query across the network commonly takes 1 to 10 milliseconds or more.
Caching appears at every layer: the browser, CDN, reverse proxy, application memory, distributed caches like Redis, and the database's own buffer pool. Each layer trades freshness for speed, so the core design question is always: how stale is acceptable?
Books you use every day stay on your desk. Rarely used books stay in the library. When your desk is full, you return the book you have not touched in the longest time. That is an LRU cache.
When caching helps
- Reads greatly outnumber writes (for example 100:1 on product pages).
- The same data is requested repeatedly (skewed access: a few items are very popular).
- The source is slow or expensive: complex queries, external APIs, rendering, ML inference.
- Slightly stale data is acceptable for a short time.
Where it shows up in interviews
Recognize it when: reads outnumber writes 10:1 or more.
- Design a news feed
- Design a product catalog
- Design a URL shortener's redirects
Recognize it when: results are costly to compute but reused.
- Design a leaderboard
- Design a recommendation service
Where it is used in real software
Facebook serves billions of reads per second from memcache clusters in front of MySQL.
The default cache for sessions, hot objects, rate limits, and leaderboards in most cloud architectures.
Cloudflare, Akamai, and CloudFront cache static and dynamic content at edge locations close to users.
Key terms
- Hit / miss
- A hit finds data in the cache; a miss must fetch from the source.
- Hit ratio
- hits / (hits + misses). A higher ratio means fewer slow source calls.
- TTL
- Time-to-live: how long an entry stays valid before expiring.
- Eviction
- Removing entries when the cache is full, using a policy like LRU or LFU.
- Invalidation
- Removing or updating cached data when the source changes.
Cache-aside (the most common pattern)
- 1Check the cache
The application looks up the key, for example user:42.
- 2Return on hit
If present, return it immediately. No database call is made.
- 3Load on miss
On a miss, query the database for the data.
- 4Populate the cache
Store the result with a TTL so it expires even if invalidation fails.
- 5Invalidate on write
When the data changes, write to the database first, then delete the cache key so the next read reloads fresh data.
Why the hit ratio matters
Cache latency 1 ms, database latency 50 ms, 10,000 requests per second.
| Hit ratio | Average latency | DB queries/sec | Result |
|---|---|---|---|
| 0% | 50 ms | 10,000 | Database saturates |
| 80% | 0.8 + 10 = 10.8 ms | 2,000 | 5x less database load |
| 95% | 0.95 + 2.5 = 3.45 ms | 500 | 20x less database load |
| 99% | 0.99 + 0.5 = 1.49 ms | 100 | 100x less database load |
NOWHit ratio: 0% | Average latency: 50 ms | DB queries/sec: 10,000 | Result: Database saturates
Average latency = hit% x cache latency + miss% x (cache + database latency). Going from 95% to 99% cuts database load by 5x, which is why tuning TTLs and key design matters.
Implementation
async function getUser(id: string): Promise<User> { const key = `user:${id}`; const cached = await redis.get(key); if (cached) return JSON.parse(cached); // hit const user = await db.users.findById(id); // miss await redis.set(key, JSON.stringify(user), "EX", 300); // 5-minute TTL return user;} async function updateUser(id: string, changes: Partial<User>) { await db.users.update(id, changes); // 1. source of truth first await redis.del(`user:${id}`); // 2. invalidate, do not overwrite}Complexity and performance
CPU cache reference.
Main memory reference.
Mostly network round trip.
Depends on indexes, load, and disk.
Hash map plus doubly linked list.
Trade-offs
Longer TTLs raise hit ratio but serve staler data. Choose TTL per data type: prices might need seconds, profile pictures can use hours.
Deleting the key is safer: two concurrent writers updating the cache can leave the older value last. Deleting lets the next read load the current truth.
In-process caches are fastest but duplicate memory and diverge across servers. Redis or Memcached is shared and consistent across servers but adds a network hop.
Cache the hot working set, not everything. Measure the hit ratio before adding memory.
Variants and related techniques
The cache library loads from the database on a miss, so application code only talks to the cache.
Writes go to the cache and database together. Reads are always fresh; writes are slower.
Writes go to the cache and are flushed to the database later. Very fast, but data can be lost if the cache fails.
LRU evicts least recently used, LFU evicts least frequently used, and FIFO evicts oldest inserted. LRU is a strong default.
Common mistakes
- Cache stampede: a hot key expires and thousands of requests hit the database at once.
Fix: Use request coalescing or a lock per key, add TTL jitter, and refresh hot keys early.
- Cache penetration: repeated requests for keys that do not exist.
Fix: Cache negative results briefly or use a Bloom filter.
- No TTL on entries.
Fix: Always set a TTL as a safety net against missed invalidations.
- Caching personalized data under a shared key.
Fix: Include the user or tenant in the key and never cache authenticated responses at a public CDN.
Interview questions
How do you keep the cache consistent with the database?
Use cache-aside with write-then-delete, short TTLs as a backstop, and for stricter needs publish change events (CDC) that invalidate keys. Perfect consistency requires giving up some caching benefit.
What would you cache in a social feed?
Precomputed feed IDs per user, post objects by ID, and counts. Keep each separately so a post edit invalidates one key rather than every feed.
How do you handle a very hot key?
Replicate it across cache nodes or keep a small in-process copy, and add request coalescing so one miss does not create a thundering herd.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| LRU Cache | Medium | Implement O(1) get and put. |
| LFU Cache | Hard | Frequency buckets. |
| Design a product catalog read path | Medium | Layers: CDN, Redis, database. |