Fundamentals
What is a Cache?
A cache is a faster storage layer that temporarily keeps frequently accessed or expensive-to-compute data close to the consumer.
Imagine your application needs a user profile. Reading that profile from a distributed database may take significantly longer than reading it from an in-memory cache.
Without Cache
Every request reaches the database or downstream service.
❌ Higher latency
❌ More database load
❌ Harder to absorb traffic spikes
With Cache
Frequently used values can be served from a faster layer.
✅ Lower latency
✅ Less database pressure
✅ Better burst handling
Caching does not make the database unnecessary
Architecture
Where Can We Cache?
Caching can exist at several layers. Large systems often combine multiple cache layers rather than relying on one Redis cluster.
User
Browser / Mobile App
Browser / CDN Cache
Static assets, images, public content
Application Servers
Local in-memory cache
Distributed Cache
Redis / Memcached
Persistent Database
Source of truth
Browser Cache
CSS, JavaScript, fonts, API responses and other client-cacheable resources.
CDN / Edge Cache
Static and public content cached geographically close to users.
Local Application Cache
Per-instance in-memory cache with extremely low latency.
Distributed Cache
A shared cache such as Redis accessible across application instances.
Database Cache
Buffer pools, query caches and database-managed memory optimizations.
Computed Result Cache
Store expensive calculations, search results, recommendations or rendered responses.
Core Interview Topic
Caching Strategies
An interviewer usually expects you to explain not only that you will use Redis, but how reads and writes interact with the cache.
Cache Aside
Lazy Loading
Read
Application checks cache first. On miss, it reads the database and writes the result into cache.
Write
Application writes directly to the database and usually invalidates the cached key.
Good for: Read-heavy systems where not every database row needs to be cached.
Trade-off: First request after a miss is slower. Stale data is possible if invalidation fails.
Read Through
Cache-managed reads
Read
Application asks the cache. The cache itself loads missing data from the underlying store.
Write
Depends on the cache implementation and write strategy.
Good for: Systems wanting caching logic abstracted away from application code.
Trade-off: Needs cache/library support for loading data from the source.
Write Through
Synchronous write
Read
Reads usually come from cache.
Write
Every write updates both the cache and persistent database before success is returned.
Good for: Systems where cache and database consistency are important.
Trade-off: Higher write latency because two systems must be updated.
Write Back
Write Behind
Read
Usually served from cache.
Write
Write reaches cache first. Database persistence happens asynchronously.
Good for: High write throughput where small persistence delays are acceptable.
Trade-off: Data can be lost if the cache fails before the asynchronous database write.
Refresh Ahead
Proactive refresh
Read
Frequently accessed data is refreshed before its TTL expires.
Write
Usually combined with another strategy such as cache-aside.
Good for: Very hot predictable keys where expiration misses would cause spikes.
Trade-off: Can refresh data that nobody ends up requesting.
Cache-Aside Flow
Client
API
Redis
GET user:42
Database
Fetch user → populate Redis → return
async function getUser(userId) {
const key = `user:${userId}`;
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached);
}
const user = await database.getUser(userId);
if (!user) {
return null;
}
await redis.set(
key,
JSON.stringify(user),
"EX",
300
);
return user;
}Memory Management
Cache Eviction Policies
Cache memory is finite. When it fills up, the system must decide what data to remove.
| Policy | Meaning | Useful When |
|---|---|---|
| LRU | Least Recently Used | Recent access predicts future access. |
| LFU | Least Frequently Used | Frequently accessed objects should remain cached. |
| FIFO | First In First Out | Simple eviction is enough. |
| TTL | Time To Live | Data becomes stale after a known period. |
| Random | Random eviction | Very simple and low-overhead policy is acceptable. |
TTL and eviction are different
The Hard Part
Cache Invalidation
Caching is easy when data never changes. The difficult part is keeping cached data acceptably fresh after writes.
Delete on Write
Update the database, then delete the related cache entry. The next read repopulates it.
Update Cache
Update the database and immediately update the cached copy.
TTL Expiry
Allow stale data for a bounded time and let the cache expire naturally.
async function updateUser(userId, payload) {
await database.updateUser(
userId,
payload
);
// Remove potentially stale data.
await redis.del(
`user:${userId}`
);
return { success: true };
}What if DB update succeeds but cache delete fails?
High Traffic
Cache Stampede / Thundering Herd
A cache stampede happens when a hot key expires and a large number of requests simultaneously discover the cache miss.
Req 1
Req 2
Req 3
Req 4
Req 5
Redis
hot:key expired → MISS
Database
Suddenly receives thousands of identical expensive queries
Ways to Stop a Stampede
Single Flight / Mutex
Only one request rebuilds the value. Other requests wait for that result.
TTL Jitter
Add random variation to expiry times so thousands of keys do not expire simultaneously.
Stale While Revalidate
Serve a slightly stale value while one background worker refreshes it.
Refresh Ahead
Refresh a highly popular key before the TTL actually expires.
Request Coalescing
Merge identical concurrent cache-miss requests into one backend operation.
Rate Limit / Queue
Protect the database by controlling how much miss traffic is allowed through.
const BASE_TTL = 300;
// Random additional expiry time.
// Keys will not all expire together.
const jitter =
Math.floor(Math.random() * 60);
await redis.set(
key,
value,
"EX",
BASE_TTL + jitter
);Hot Key Problem
The Celebrity Problem
Imagine a celebrity publishes a post and tens of millions of users request the same profile or post. One cache key can become far hotter than everything else.
User
User
User
User
celebrity:123
One extremely hot Redis key / shard
Local L1 Cache
Cache the hot object inside application instances so many requests never reach Redis.
Replication
Spread read traffic across replicas when the data can be safely read from multiple nodes.
Key Replication
For extreme read hotspots, intentionally maintain multiple copies such as hot:123:0, hot:123:1, etc.
CDN / Edge
For public content, move requests closer to users and keep them away from the origin entirely.
Longer TTL
If freshness requirements allow it, avoid repeatedly rebuilding extremely popular content.
Pre-Warming
Populate known hot data before a predictable event or traffic surge.
Scale
How Do We Handle Millions of Requests?
One Redis node is not the architecture. At large scale we reduce traffic at every possible layer before it reaches the primary database.
Millions of Users
Web / Mobile
CDN / Edge
Absorb static + cacheable traffic
Load Balancer
Spread traffic
API 1
L1 cache
API 2
L1 cache
API N
L1 cache
Redis Cluster
Distributed L2 cache
Database Cluster
Primary + replicas / partitions
The goal is traffic reduction
Distributed Cache
Designing Redis Properly
Redis is fast, but poor key design, unlimited TTLs or one hot shard can still create serious production problems.
Key Naming
user:42
user:42:profile
product:1001
movie:778:show:992
show:992:seat:A10
show:992:seat:A10:lock
feed:user:42:v3Use Namespaces
Keys such as user:42 and product:42 avoid collisions and are easier to inspect.
Always Think About TTL
Permanent cache entries often become stale or consume memory indefinitely.
Avoid Giant Values
Huge serialized objects increase network, CPU and memory cost.
Monitor Hit Ratio
A cache with a very low hit ratio may add complexity without meaningful benefit.
Watch Hot Keys
One disproportionately popular key can overload a shard even when total cluster capacity looks healthy.
Plan Failure Mode
Ask what your application does when Redis is unavailable.
System Design Case Study
BookMyShow-Style Seat Locking with Redis
Let's apply caching and distributed locking concepts to a high-concurrency ticket booking system.
Important
Seat State Machine
AVAILABLE
Seat can be selected
HELD
Temporary Redis lock
BOOKED
Persisted after successful booking
Booking Flow
User selects A10
Seat Reservation API
Validate show + seat
Atomic Redis Lock
SET seat:992:A10:lock token NX PX 300000
5 Minute Seat Hold
Other users see seat as unavailable
Payment
Idempotent booking flow
Persistent DB
Atomically mark seat BOOKED
const lockKey =
`show:${showId}:seat:${seatId}:lock`;
const token = crypto.randomUUID();
const acquired = await redis.set(
lockKey,
token,
"NX",
"PX",
5 * 60 * 1000
);
if (!acquired) {
throw new Error(
"Seat already held by another user"
);
}
// Continue checkout...
Why Store a Unique Token?
Imagine User A obtains a lock. The lock expires. Then User B obtains a new lock for the same seat. User A must not accidentally delete User B's lock during cleanup.
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
endWhat Happens When 100 People Click A10?
U1
U2
U3
U4
U100
Redis Atomic SET NX
Only one request creates the lock
Winner
One request acquires the seat hold and proceeds to checkout.
Everyone Else
Redis refuses the `NX` operation and the API returns seat unavailable.
But Redis Must Not Be Your Only Protection
The cache lock protects the high-concurrency checkout experience, but the final booking should still be protected by the persistent data layer.
UPDATE seats
SET
status = 'BOOKED',
booking_id = :bookingId
WHERE
show_id = :showId
AND seat_id = :seatId
AND status = 'AVAILABLE';Defense in depth
Payment Success vs Failure
Payment Success
1. Verify payment
2. Idempotently create booking
3. Persist seat as BOOKED
4. Remove temporary lock
5. Publish booking event
Payment Failure
1. Mark payment attempt failed
2. Safely release the Redis lock
3. Or allow TTL to expire
4. Seat becomes available again
5. Notify active clients
Production Engineering
Caching Failure Scenarios
A strong system design answer explains what happens when the happy path fails.
Redis Goes Down
Fail open or fail closed depending on the use case. Normal profile reads may temporarily fall back to the DB with strict rate limiting. Seat locks require much stricter correctness handling.
Cache and DB Disagree
Treat the database as authoritative. Use TTL, invalidation retries, versioning or events to repair stale cache state.
All Keys Expire Together
Use TTL jitter, proactive refresh and staggered warm-up.
One Key Becomes Extremely Hot
Use local cache, replicas, CDN, key replication or precomputed responses depending on the workload.
Redis Memory Is Full
Configure appropriate max-memory policy, TTLs and alerts. Monitor eviction rate and memory fragmentation.
Network Partition
Define whether stale cached reads are acceptable and avoid assuming every distributed lock is an absolute correctness guarantee.
Important Metrics
Trade-offs
When Should You Not Cache?
Adding Redis to every system is not automatically good system design.
Data changes extremely frequently and stale reads are unacceptable.
The underlying database query is already cheap and traffic is low.
The expected cache hit ratio is extremely small.
The additional operational complexity is greater than the performance benefit.
The value is highly sensitive and should not be duplicated without a clear security model.
Strong transactional consistency is required on every read.
Interview Preparation
Caching Questions You Should Be Able to Answer
If you can explain these clearly, including trade-offs, you are in a strong position for most HLD interviews.
What problem does caching solve?
What is the difference between cache-aside and read-through?
When would you choose write-through over write-back?
What is a cache stampede?
How would you prevent a thundering herd?
What is TTL jitter?
What is the celebrity / hot-key problem?
How do you invalidate cache after a database update?
What happens if the database update succeeds but cache invalidation fails?
What happens if Redis is unavailable?
How do you cache data across multiple application servers?
What is the difference between an L1 local cache and an L2 distributed cache?
LRU vs LFU — when would you use each?
How would you design a cache for millions of requests per second?
How would you prevent double booking using Redis?
Why should the database still protect final booking correctness?
How do idempotency and caching interact in a payment or booking system?
What metrics would you monitor for Redis?
Quick Revision
Frequently Asked Questions
Short answers to some of the most common caching questions.
What is caching in system design?
Caching stores frequently used data in a faster storage layer so future requests can avoid repeatedly accessing a slower database, service or remote resource.
What is a cache stampede?
A cache stampede happens when a popular cached value expires and many requests simultaneously miss the cache and hit the underlying database or service.
What is the celebrity or hot-key problem?
The celebrity problem occurs when one key receives disproportionately high traffic, causing a single cache shard, node or backend dependency to become overloaded.
Why is Redis commonly used for caching?
Redis provides fast in-memory access, TTL support, atomic commands, multiple data structures and clustering capabilities, making it useful for caching and short-lived distributed state.
Can Redis prevent double booking?
Redis can be used for short-lived atomic seat holds, but the persistent database should remain the source of truth and the booking flow should also use idempotency and database-level consistency protections.
Want to master complete System Design?
Go beyond caching. Learn requirements, APIs, databases, scaling, queues, locking, concurrency, failure handling and complete interview-style designs such as BookMyShow and other large-scale systems.
Continue Learning