Caching Strategies

9 min read

Reading Progress0%
System Design Index
Start Here
Tier 1 -- Building Blocks
Tier 2 -- Core Systems
URL Shortener — System Design InterviewFree
Pastebin — System Design InterviewFree
News Feed System — System Design InterviewFree
Chat System (WhatsApp/Messenger) — System Design InterviewFree
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative
System Design Index
Start Here
Tier 1 -- Building Blocks
Tier 2 -- Core Systems
URL Shortener — System Design InterviewFree
Pastebin — System Design InterviewFree
News Feed System — System Design InterviewFree
Chat System (WhatsApp/Messenger) — System Design InterviewFree
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative

Caching Strategies

Tier 1 — Building Block


1. What Is It?

A cache is a fast, in-memory data store that sits between your application and a slower, durable data store (database, file system, external API). Its purpose is simple: serve frequently accessed data from memory instead of paying the and I/O cost of hitting the source of truth on every request.

Without caching, even a moderately loaded service can overwhelm a relational database. A single PostgreSQL node typically handles ~10K–30K simple queries per second. A Redis node handles ~100K–200K ops/sec. Caching bridges that gap by absorbing read traffic before it reaches the database, dramatically reducing (sub-millisecond from cache vs. 5–50ms from DB) and protecting the database from overload.

The "strategy" is the policy that defines when cache entries are written, when they are invalidated, and how the application interacts with both the cache and the database.


QUICK CHECK

A backend service is experiencing slow response times because every API request hits the PostgreSQL database directly, even for data that rarely changes. Which of the following best explains why introducing an in-memory cache like Redis would improve this situation?

Choose one answer

2. How It Works

The three core write strategies differ in when and who writes to the database:

Cache-Aside (Look-Aside / Lazy Loading)

The application code manually manages the cache. On a read:

  1. Check cache — if hit, return data.
  2. If miss, read from database.
  3. Write result to cache with a (Time-To-Live) — an expiration time after which the cached entry is automatically deleted.
  4. Return data to caller.

On a write:

  1. Write directly to the database.
  2. Invalidate (delete) the cache entry.
Read Path:
  App → Cache (miss) → DB → App → Cache (write + TTL)

Write Path:
  App → DB → Invalidate Cache Key

Write-Through

Every write goes through the cache — the application writes to the cache, and the cache synchronously writes to the database before acknowledging success.

Write Path:
  App → Cache → DB (synchronous) → ACK

Write-Back (Write-Behind)

The application writes to the cache, and the cache asynchronously flushes dirty entries to the database later (batched). High write , but data loss risk if cache crashes before flush.

Write Path:
  App → Cache (immediate ACK) → [async batch] → DB

QUICK CHECK

A backend service uses cache-aside (lazy loading) to cache user profile data. A developer updates a user's email address in the database. What should happen to the cache entry for that user immediately after the database write succeeds?

Choose one answer

3. Variants & Comparisons

ApproachHow It WorksProsConsBest For
Cache-Aside (Look-Aside)App checks cache, populates on miss, invalidates on writeSimple; only caches what's read; cache failure is non-fatalCache miss penalty on cold start; potential stale data between write and invalidateRead-heavy workloads; data that isn't always needed
Write-ThroughEvery write goes cache → DB synchronouslyCache always consistent with DB; no stale readsWrite latency penalty (two writes: cache + DB); wastes cache space for write-only dataRead-after-write consistency critical; financial records
Write-BackWrite to cache, async flush to DBVery high write throughput; batched DB writes reduce I/OData loss if cache crashes before flush; complexity of tracking dirty entriesHigh write throughput tolerance for some data loss (analytics, logs, counters)
Read-ThroughCache sits in front; on miss, cache itself fetches from DBTransparent to applicationCache library complexity; cold start missesApps wanting to abstract caching from business logic
Refresh-AheadCache proactively refreshes entries before TTL expiresEliminates miss latency for popular entriesFetches data that may never be read againHigh-traffic items with predictable access patterns

Specific Technologies:

  • Redis: In-memory, supports strings/hashes/sets/sorted sets/streams. ~100K–200K ops/sec per node. Supports , clustering, persistence (RDB snapshots + AOF). Use for session data, leaderboards, , distributed locks.
  • Memcached: Pure key-value, no persistence, multi-threaded. ~200K–500K simple gets/sec. Simpler than Redis — use when you need raw for simple objects and don't need Redis data structures.
  • Caffeine (Java in-process): JVM-local LRU/LFU cache. Zero network . Use for per-instance local caches (e.g., config, small lookup tables). Not shared across instances.
  • Varnish: HTTP cache. Layer 7. Caches full HTTP responses. Use for -like behavior at the edge without a full .

QUICK CHECK

A payments platform needs to ensure that immediately after a balance update is written, any subsequent read reflects the new balance — stale reads are unacceptable. Which caching strategy best satisfies this requirement, and what is its key trade-off?

Choose one answer

4. When to Use It (and When NOT To)

Use Caching When:

  • Read-to-write ratio > 5:1 — cache hit rate will be high enough to justify the complexity
  • Data has temporal locality — recently accessed data is likely to be accessed again soon
  • DB is the bottleneck — measured DB CPU/IO at >60% utilization under load
  • Acceptable staleness window exists — even 1–5 minutes of stale data is OK (profile pages, product listings)
  • Computation is expensive — caching aggregation query results, rendered templates, ML model outputs

Decision triggers:

  • "If QPS > 50% of your DB's max → add read cache ()"
  • "If is DB-bound (>50ms) → cache the hot query set"
  • "If you have > 10K DAU with repeated reads of the same objects → with 5-min "

Do NOT Use Caching When:

  • Every request is unique — cache hit rate ~0%; you're just adding overhead
  • is required — financial balances, inventory counts during checkout (stale cache = oversell)
  • Write-heavy with no repeated reads — you'll thrash the cache constantly
  • Data changes faster than — cache is always stale; misleading at best

Anti-patterns:

  • Cache stampede / : TTL expires for a popular key; 10K concurrent requests all miss simultaneously and hammer the DB. Fix: probabilistic early expiration, mutex lock on populate, or background refresh.
  • Cache poisoning: Writing bad data to cache (e.g., null from a downstream API). Fix: never cache null/errors, or cache with very short TTL.
  • Over-caching: Caching everything including writes leads to memory pressure and stale data everywhere.
  • No TTL: Cache entries grow forever and consume all memory. Always set TTL.

QUICK CHECK

An e-commerce platform's checkout service reads real-time inventory counts to prevent overselling. The DB is running at 75% CPU utilization under peak load, and P99 latency is around 80ms. An engineer proposes adding a cache-aside layer with a 5-minute TTL to reduce DB pressure. Why is this a poor fit for caching?

Choose one answer

5. Real-World Usage

Facebook (Memcached at scale): Facebook's "Scaling Memcache at Facebook" paper (2013) describes serving trillions of reads/day from Memcached clusters. They use , with invalidation messages sent via McSqueal (a daemon that tails MySQL binlogs and sends delete notifications). At peak, they served >1 billion requests/sec across all Memcached pools. The key insight: they tolerate in exchange for extreme .

Twitter (Redis for timelines): Twitter pre-computes user timelines (fan-out on write — when a user tweets, the system pushes that tweet into the timeline cache of every follower) and stores them in Redis sorted sets, keyed by user ID with tweet IDs as members. For users following >10K accounts (celebrities), they switch to fan-out on read (assembling the timeline from the DB at query time, instead of pre-computing it) because pre-computing for 100M followers per tweet is prohibitively expensive. This is a real-world example of choosing cache strategy based on data shape and scale.

Amazon DynamoDB DAX: DynamoDB Accelerator (DAX) is a for DynamoDB. Every write to DynamoDB goes through DAX first. Reads that hit DAX return in ~microseconds vs. ~milliseconds from DynamoDB. Amazon uses this for product catalog reads where consistency matters but writes are infrequent relative to reads.


QUICK CHECK

A social media platform lets users follow celebrities who have tens of millions of followers. When a celebrity posts, the system must update timelines for all their followers. Pre-computing and pushing the post into every follower's cached timeline becomes prohibitively expensive at this scale. Which strategy best addresses this problem?

Choose one answer

6. Interview Cheat Sheet

Key sentences to demonstrate depth:

  1. " is my default choice because the application controls what gets cached — only read data that was actually needed gets populated, which avoids wasting cache memory on cold data."
  2. "Write-through trades write for read consistency — every write pays the two-write cost (cache + DB), so I'd only use it when read-after-write correctness outweighs the penalty."
  3. "Write-back maximizes write by making DB writes async and batched, but the dirty cache window means you can lose writes on crash — acceptable for metrics and counters, not for financial data."
  4. "Cache stampede is the most common cache failure mode: a popular expires, N concurrent requests all miss, all hit the DB simultaneously. The fix is probabilistic jitter or a cache populate mutex."
  5. " is one of the two hard problems in computer science (along with naming). My rule: invalidate on write (DEL the key), don't update — it's simpler and avoids race conditions between update and concurrent reads."

Common follow-up questions:

QuestionConcise Answer
"What is cache eviction policy?"LRU (least recently used) for general workloads; LFU (least frequently used) for content with long tail of rare requests; TTL-based expiry for time-sensitive data. Redis supports multiple policies (allkeys-lru, volatile-lru, etc.)
"How do you handle cache consistency after a write?"Invalidate on write (DEL), not update-in-place. Avoids race where concurrent reader sees partially-updated cache. Accept brief stale window until next read re-populates.
"What if Redis goes down?"With cache-aside: traffic falls back to DB (DB must be sized to handle ~5x normal load as a safety margin). With write-through/write-back: data loss risk — need Redis persistence (AOF) + standby replica.
"How do you size your cache?"Cache the hot set: typically 20% of data serves 80% of traffic (Pareto). Start with enough memory to hold the top 20% of objects by access frequency. Monitor hit rate — target >90% hit rate.
"What's a hot key problem?"A single cache key (e.g., Justin Bieber's profile) gets hammered by millions of requests/sec, overloading that Redis shard. Fix: replicate the key across N cache nodes; route requests round-robin across replicas.

Connections to other building blocks:

  • : When you have a Redis cluster, determines which node holds each key. Essential for avoiding rehashing all keys when adding/removing nodes.
  • Read Replicas: Caching and read replicas are complementary strategies — cache handles repeated reads of the same objects; read replicas scale the database for diverse query patterns.
  • : A is essentially a globally distributed cache for HTTP responses. Same semantics: miss → origin pull → store at edge → serve from edge on subsequent requests.
  • CAP Theorem: Caches are inherently AP (available + partition tolerant) — they sacrifice consistency (stale data) to serve requests fast even when the DB is slow/down.
Glossary History

Click dotted jargon to save explanations here.

Glossary History

Click dotted jargon to save explanations here.