System Design Interview Questions: Caching Strategies

Reviewed by Mark Dickie · Last updated

Caching strategies are techniques that store frequently accessed data closer to the consumer to reduce latency, database load, and cost. For a system design interview, you should know the four core write patterns (cache-aside, write-through, write-back, write-around), the common eviction policies (LRU, LFU, FIFO), and the invalidation tradeoffs that determine consistency. Interviewers want to see you reason about when a cache helps, what staleness your system can tolerate, and how cache-miss thundering herds degrade under load. The questions below let you check these fundamentals before you walk into the whiteboard.

What write patterns should I know for a caching interview?

PatternWrite pathRead pathStaleness risk
Cache-asideApp writes to DB, then invalidates cacheApp checks cache, falls back to DB on missModerate — stale reads between invalidation and next load
Write-throughApp writes to cache and DB togetherCache hit serves all readsLow — cache stays consistent with DB
Write-backApp writes to cache only; cache flushes to DB laterCache hit serves readsHigh — data loss risk if cache fails before flush
Write-aroundApp writes to DB only, cache loads on read missApp checks cache, falls back to DBModerate — first read after write is a miss

How do eviction policies affect cache hit rate?

  1. LRU evicts the least recently used entry, which works well when access patterns follow recency — most reads touch recently fetched data.
  2. LFU evicts the least frequently used entry, which is better when some keys stay popular over time regardless of recency.
  3. FIFO evicts the oldest inserted entry. It is the simplest to implement but ignores access frequency entirely.
  4. TTL-based eviction discards entries after a fixed time. This is useful for data that goes stale on a schedule, though it does not track access patterns.

What cache invalidation question comes up most often?

Cache invalidation is the part candidates stumble on because it forces a consistency vs. latency tradeoff. You should be ready to explain write-through invalidation, where the cache stays in sync with DB writes; explicit invalidation, where the application deletes cache keys after a DB update; and TTL-based expiry, where cache entries expire after a set window.

The hardest follow-up asks how you handle invalidation in a distributed cache with eventual consistency. Your answer should address cache stampedes, where many requests miss the cache at once and all hit the database. Common mitigations are request coalescing, probabilistic early expiry, and locking the cache-miss path so only one request repopulates the key.

Key facts

  • Tarmac has 14 System Design interview questions on this topic, 10 of them on this page, at difficulty 2–4 of 5.
  • Tarmac tracked 4,937 job postings asking for System Design in August 2026.
  • Roles asking for System Design advertise a median base salary of US$182,500, across 1,211 job postings as of August 2026.
  • Tarmac last reviewed these System Design interview questions on 14 September 2026.

At a glance

Questions10 shown · 14 in the bank
Difficulty2–4 of 5
FormatsMultiple answer, Multiple choice, Short answer, Flashcard, Ordering, Design exercise

What you'll review

  1. cdn edge
  2. caching strategies
  3. cache eviction
  4. ai system design
  5. id generation
  6. rate limiting

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

System Design/sd-fundamentals/cdn-edge

Your global app serves large static assets (images, JS bundles, video) and origin egress plus latency are hurting. You put a CDN / edge cache in front. Which outcomes are correct expectations of this change?#

Options

Pick every one that applies.

Show answer

The correct expectations are that cached assets are served from edge PoPs close to users (cutting round-trip latency), origin load and egress drop because the CDN absorbs repeat requests for cacheable content, and Cache-Control/TTL headers govern how long edges serve content before revalidating. Highly personalized per-user responses are not trivially cacheable at a shared edge, and edges do not instantly reflect origin changes — stale objects live until TTL or an explicit purge.

Why:

A CDN caches content at edge points of presence near users (a), so it lowers latency and offloads repeat requests from the origin, cutting load and egress (b), with freshness controlled by Cache-Control/TTL and revalidation (c). The wrong options are classic edge-caching traps. Personalized, per-user dynamic responses are generally not cacheable at a shared edge (d) — caching them risks leaking one user's data to another, so they need private/no-store handling or edge-compute personalization, not naive caching. And edges do not instantly reflect origin changes (e): cached objects live until their TTL or an explicit purge/invalidation, which is precisely why a bad asset deploy can keep serving stale content until you invalidate the cache or bust the URL.

System Design/sd-fundamentals/caching-strategies

A read-heavy product catalog uses a cache in front of the database. The team wants fresh reads immediately after a write with the lowest steady-state read latency, accepting slightly slower writes. Which caching strategy best fits?#

Options

Show answer

Use a write-through cache: write to the cache and the database synchronously on every write. The just-written value is already in cache for the next read, giving fresh reads immediately at the cost of a slightly slower write — exactly the trade described. Cache-aside only populates on a read miss, and TTL or write-behind strategies both permit a window of staleness.

Why:

Write-through updates the cache and the database in the same write path, so the just-written value is already in cache for the next read — you get fresh reads immediately at the cost of a slower write, exactly the trade the team accepted. Plain cache-aside only populates the cache on a read miss, so the entry written can be stale until the next miss (and a delete-on-write is needed to avoid serving the old value). Write-behind acknowledges before the DB is durable, trading consistency and durability for write speed — the opposite of the stated priority. TTL-only caching guarantees staleness for up to the TTL window, which violates the "fresh immediately" requirement.

System Design/sd-architecture/ai-system-design

You operate a customer-facing endpoint backed by an LLM. The model inference call dominates both p95 latency and per-request cost. Which combination of system-level techniques most directly attacks both at once?#

Options

Show answer

Combine three system-level levers: stream tokens over SSE to cut time-to-first-token, cache responses to repeated or similar prompts to skip inference entirely, and route simple requests to a small model while escalating only when needed. These change how often you invoke the model, which model you invoke, and how you deliver the result. Adding replicas, blanket CDN caching, or raising the output token limit each fail here.

Why:

The inference call is the lever, so the wins come from changing how often you invoke it, which model you invoke, and how you deliver the result. Streaming (SSE) doesn't speed up total generation but slashes perceived latency by showing the first tokens immediately. Prompt/semantic caching returns a stored answer for repeated or near-duplicate prompts, skipping inference entirely — a direct latency and cost win. Model routing (cheap small model first, escalate to a larger model only when needed) cuts average cost without hurting quality on easy requests. The distractors miss: extra replicas don't speed up a GPU-bound call and raising the timeout makes latency worse (b); a blanket 24h CDN cache serves wrong/stale answers because prompts vary per user (c); and raising the output token limit increases tokens generated, raising latency and cost per call (d).

System Design/sd-fundamentals/caching-strategies

Explain cache-aside versus write-through caching, and give one failure mode of each.#

Show answer

With cache-aside the application reads from the cache and, on a miss, loads from the database and populates the cache itself; writes update the database and invalidate or delete the cached entry. Its failure mode is a stale read: between a write that fails to invalidate (or two concurrent reads racing a write) the cache can serve outdated data. With write-through the application writes through the cache layer, which synchronously writes to the database before acknowledging, so the cache is always consistent on write. Its failure mode is added write latency and the fact that data nobody reads still gets cached, wasting memory.

Why:

Cache-aside (lazy loading) keeps cache population in the application: read the cache, fall back to the store on a miss, and explicitly invalidate on write — simple and resilient (a cache outage just means more DB load) but prone to stale reads under races. Write-through routes writes through the cache so it is always fresh, at the cost of write latency and caching cold data; it pairs well with a read-through path. The right choice depends on read/write ratio and tolerance for staleness.

System Design/sd-fundamentals/caching-strategies

Describe the cache-aside (lazy-loading) caching strategy and its main failure mode.#

Show answer

In cache-aside the application code owns the cache: on a read it checks the cache first, and on a miss it loads from the database, populates the cache, and returns. Writes go to the database and then invalidate (or update) the cache entry. Its main failure mode is the thundering herd / cache stampede — when a hot key expires, many concurrent requests all miss and hammer the database at once. Mitigate with request coalescing (single-flight), a short lock per key, or probabilistic early refresh.

Why:

Cache-aside is the most common strategy because the app stays in control and the cache only ever holds data that was actually requested. The trade-offs are an extra round trip on misses and the need to manage invalidation carefully, since stale entries linger until they expire or are explicitly evicted.

System Design/sd-fundamentals/caching-strategies

Order the components a cacheable read request passes through on its way to the data, from the client outward.#

Put these in order

Show answer

A cacheable read passes through progressively narrowing caches before reaching the data:

  1. The client request hits the nearest CDN / edge point of presence.
  2. On a CDN miss it forwards to the regional load balancer.
  3. The load balancer routes to a healthy application server.
  4. The app checks the in-memory cache (e.g. Redis) for the key.
  5. On a cache miss it queries the database of record.
Why:

Each layer exists to absorb load before it reaches the next, more expensive one: the CDN serves cacheable responses at the edge, the load balancer spreads survivors across stateless app servers, the app cache shields the database from hot reads, and only genuine misses reach the database. Designing the read path as a cascade of progressively-narrowing caches is the core of scaling reads cheaply.

System Design/sd-fundamentals/caching-strategies

Order the steps of a write-through cache handling a write, from the application call to acknowledging the client.#

Put these in order

Show answer

A write-through cache writes both stores before acknowledging:

  1. The application issues a write for a given key.
  2. The cache layer writes the new value into the cache.
  3. The same write is synchronously persisted to the database.
  4. Only after both succeed is the write acknowledged to the application.
Why:

Write-through keeps the cache and database in lock-step by writing both before acknowledging, so a subsequent read is guaranteed a warm, consistent cache entry. The trade-off is higher write latency (two synchronous writes) and writing data that may never be read; contrast with write-back, which acks after the cache write and flushes to the database asynchronously for lower latency but a durability risk.

System Design/sd-data/id-generation

Design a URL shortener (think Bitly / TinyURL).#

Show answer

Requirements. Two operations dominate: create (write a mapping, optionally with a custom alias and expiry) and redirect (look up a code and 30x to the long URL). The system is overwhelmingly read-heavy (100:1), so the redirect path is what we optimise. I'd confirm: are custom aliases required (yes), do links expire (optional TTL), and how precise must click counts be (approximate/eventually-consistent is fine). I'd use a 302 (temporary) redirect so we keep serving redirects through our system and can still count clicks; a 301 would let browsers cache and bypass us.

Capacity. ~40 writes/sec average, ~200/sec peak; ~4K reads/sec average, ~20K peak. Storage: ~6B links over 5 years; at ~500 bytes/row (code, URL, metadata) that's ~3 TB — comfortably shardable. Keyspace: base62 with 7 characters gives 62⁷ ≈ 3.5×10¹² codes, far more than 6B, so 7 chars (often padded to a fixed length) is plenty. The hot working set (recently/ popularly accessed links) is a small fraction of 6B, so a cache of tens of GB covers the bulk of redirect traffic.

Data model. A single mapping table/collection keyed by the short code: code (PK) → long_url, created_at, expires_at, owner_id. Point lookups by primary key suit a key-value store (DynamoDB/Redis-backed) or a sharded relational table sharded by code. Click counts live separately — incrementing a counter on every redirect would put write load on the read path — so clicks are emitted as events and aggregated asynchronously.

Short-code generation. Generate a unique 64-bit id (a distributed counter handed out in ranges per server, or a Snowflake-style id) and base62-encode it to get the short code; this guarantees uniqueness with no collision checks and no hotspot from a single shared counter. Custom aliases are written directly with a uniqueness check and stored in the same table, reserving that code. (A hash-of-URL scheme is the alternative but needs collision handling and breaks idempotency for duplicate URLs.)

Read/write paths. Write: allocate id → base62 → insert mapping → return the short URL. Redirect: a load balancer / CDN fronts stateless app servers; the server does a cache-aside lookup (code → long_url) in Redis, falling back to the store on a miss and populating the cache, then returns a 302. This keeps p99 well under 100 ms for cache hits. Expiry is enforced by TTL on both the row and the cache entry. Each redirect fires a lightweight click event onto a queue (Kafka) that a consumer aggregates into per-link counts.

Bottleneck & scaling. The bottleneck is the redirect read path at peak. We scale it with cache layers (most reads never touch the store), read replicas, and sharding the mapping store by code so lookups stay single-shard. Id generation scales by handing each server an independent id range (or using Snowflake), avoiding a single global counter as a SPOF. At 10× traffic, the cache absorbs most of it; we add cache nodes and replicas and, if needed, push redirects further to the edge.

Why:

A URL shortener is the canonical warm-up design: it forces a clean separation of a heavily read-optimised redirect path from a comparatively rare write path. The two pivotal decisions are short-code generation (a base62-encoded distributed id avoids both collisions and the hotspot of a single shared counter) and caching (cache-aside on code→URL, fronted by a CDN/LB, is what meets the latency budget when reads outnumber writes 100:1). Click counting is deliberately moved off the redirect path and made asynchronous so analytics never slow a redirect. The dominant bottleneck is the redirect read path, scaled by caching, read replicas, and sharding the mapping store by code.

System Design/sd-patterns/rate-limiting

Design a distributed rate limiter for a public API gateway. Every incoming request must be checked against a per-API-key quota before it reaches a backend.#

Show answer

Requirements. The limit is per API key over a rolling window (1,000/min), checked on every request. I'd confirm: is a brief burst above the steady rate acceptable (token bucket says yes, up to the bucket size), and — most importantly — if the counter store is unavailable, do we fail open (let requests through, protect availability) or fail closed (reject, protect the backend)? For a public gateway I default to fail-open with alerting, since rate limiting is a guardrail, not the product.

Algorithm. I'd use a sliding-window counter (or token bucket). Fixed-window counters are simplest but allow ~2× the limit across a boundary: a client sends 1,000 in the last second of minute N and another 1,000 in the first second of N+1. The sliding-window counter weights the previous window's count by the overlap fraction, smoothing that out at a fraction of the cost of a full per-request log. Token bucket is the equivalent framing when you want to allow controlled bursts: refill at limit/60 per second, cap the bucket at the burst size.

Distributed counter. Counters live in a shared in-memory store (Redis) so all gateway nodes see the same count. The check must be atomic — a read-modify-write from two nodes races and under-counts — so I use INCR + EXPIRE (or a small Lua script that does increment, TTL, and the limit comparison in one round trip). The API key is the Redis key, so all operations for one key land on one shard.

Accuracy vs latency. A central store is exact but adds a network hop to every request. To hold <5 ms p99, I keep the store cell-local (one Redis per region/cell, no cross-region hop) and accept that limits are enforced per-region. If even one hop is too much, the alternative is local per-node token buckets that sync counts every few hundred ms — fast and highly available, but approximate (a key can briefly exceed the global limit by roughly the number of nodes). I'd start central-per-cell and only move to local+sync if the hop proves too costly.

Failure modes. Store down: fail open, serve requests un-limited, and alert. Hot key: a single key's counter is one Redis key on one shard, so a 200K-req/sec key hammers that shard — I'd detect it and shard that key's counter into N sub-counters (key:0..N) summed on read, or shed it at the edge. Clock skew: window algorithms key off time, so I rely on the store's clock (single source) rather than each node's wall clock.

Scaling. At ~500K ops/sec the counter store is sharded by key across a Redis cluster; because each key's ops stay on its shard, adding shards scales throughput linearly. Replicas give availability, and per-cell stores keep latency flat as we add regions. The bottleneck is always the hottest single key, handled by sub-sharding as above.

Why:

A distributed rate limiter is the canonical 'shared mutable counter in the hot path' problem. The two decisions interviewers probe are the algorithm (sliding-window counter or token bucket, because fixed-window allows a 2× burst across the window boundary) and where the counter lives (a shared atomic store is exact but adds a hop; local counters with sync are fast but approximate — the <5 ms budget forces you to pick a point on that spectrum). The atomic increment-and-check (INCR+EXPIRE or Lua) is what prevents concurrent nodes from racing and under-counting. The dominant failure modes are the fail-open/fail-closed choice when the store dies and the hot-key problem, where one key's counter overloads its single shard.

Related interview questions

Job market

See system-design salaries and hiring demand from live job postings.

The other 4 questions

This page shows 10 and marks what you pick. That's as far as a page can go. A free account opens the other 4 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.