Caching Strategies and Distributed Caching Questions
Using caches to reduce latency and load: cache-aside, read-through, write-through, and write-behind patterns, TTLs, eviction policies, and distributed caches such as Redis or Memcached. Covers cache invalidation, stampede and thundering-herd protection, and the consistency tradeoffs of caching. Focuses on where and how to cache across tiers.
Explain the purpose of caching in distributed systems. Define cache hit, miss, and hit ratio, and describe the typical benefits and tradeoffs of adding a cache to a service. Give concrete examples of workloads that benefit from caching (and why) and workloads where caching could be harmful.
Sample Answer
Direct answer
A cache is a fast, smaller copy of data kept close to where it is used, so repeated reads can be served without redoing expensive work (a database query, a network call, a heavy computation) every time. The core trade-off is speed and reduced load in exchange for the risk that the cached copy becomes stale relative to the real source of truth.
Structured elaboration
- What problems caching solves: latency (serving from memory or a nearby node is far faster than recomputing or fetching from a distant source), throughput/load (fewer requests reach the expensive backend), and cost (fewer database queries or external application programming interface (API) calls, which often cost money directly).
- Where caches typically live: client/browser (closest to the user, zero network cost on a hit), content delivery network (CDN) / edge (near the user but shared across many users), application in-memory (fast, but local to one process/instance), and a shared distributed cache like Redis or Memcached (shared across all instances in a region, one network hop away).
- Primary trade-offs: staleness (the cached copy may not reflect the latest write), added complexity (invalidation logic, cache-miss handling, monitoring another moving part), and memory cost (caches are not free storage).
- When caching helps: read-heavy workloads where the same data is requested repeatedly and can tolerate at least a little staleness, or where the underlying computation/fetch is expensive relative to a cache read.
- When caching is harmful: data that changes on every read (no repeated value to cache), workloads that are already write-heavy with low read repetition (cache churn without benefit), or correctness-critical data where any staleness is unacceptable and the added complexity of cache invalidation introduces more risk than the latency win is worth.
Worked example
An API endpoint that computes a dashboard aggregate over a large dataset in 800ms, requested 200 times per minute by the same handful of users, is a strong caching candidate: a 30-second time-to-live (TTL) cache serves nearly every request from cache after the first, cutting both latency (single-digit milliseconds instead of 800ms) and backend load by over 95 percent, for a staleness window most dashboard users will not notice. The same 30-second TTL applied to an account balance shown right after a deposit would be actively harmful, showing the user a stale, "wrong" number at the exact moment they are checking it.
Trade-offs and pitfalls
The most common mistake is caching by default rather than by evaluating whether the read pattern and staleness tolerance actually justify it; caching adds a second place data can be wrong (the cache disagreeing with the source of truth), and that failure mode does not exist at all if you never cache the data in the first place.
Explain cache hit, miss, and eviction. Describe the metrics you would monitor to understand cache health in a production system and how you would measure them. Given a cache that receives 100k requests/min and returns 80k hits, calculate the hit rate and discuss implications for capacity sizing and SLOs.
Sample Answer
Direct answer
A cache hit is a read served from the cache; a miss is a read that had to go to the origin; the hit ratio is hits divided by total requests, and it is the single most important summary number for whether a cache is doing its job.
Structured elaboration
- Hit ratio formula: hit ratio=hits+misseshits. It should be tracked as a rolling metric (e.g., per minute) rather than a single lifetime number, since it reacts to traffic pattern changes, deploys, and cache flushes.
- Eviction: when the cache is full and a new item needs room, an eviction policy (least-recently-used, LRU; least-frequently-used, LFU; and others) picks an existing item to remove; eviction rate is a separate signal from hit ratio and matters because a high eviction rate on an undersized cache is a common root cause of a lower-than-expected hit ratio.
- Measuring it in production: most cache clients and cache servers (e.g., Redis's
INFOcommand) expose hit/miss counters directly; export them to your metrics system and alert on a sustained drop, not a single noisy data point. - What the number implies for capacity sizing: a hit ratio well below what the workload's theoretical repeat-read rate suggests usually means the cache is too small (working set does not fit, so useful entries get evicted before they are reused again) rather than a fundamental mismatch between the workload and caching.
- What it implies for service-level objectives (SLOs): because a miss is typically an order of magnitude or more slower than a hit, the hit ratio directly predicts your p95/p99 latency distribution; a drop in hit ratio should be treated as a leading indicator of a latency SLO breach, not just a cache-health curiosity.
Worked example
A cache receiving 100,000 requests per minute returns 80,000 hits. Hit ratio is 80,000/100,000=0.80, or 80 percent. If a hit takes 2ms and a miss takes 150ms, the blended average latency is 0.80×2ms+0.20×150ms=1.6ms+30ms=31.6ms. If the hit ratio drops to 60 percent with the same per-hit and per-miss costs, blended latency rises to 0.60×2+0.40×150=1.2+60=61.2ms, roughly doubling despite "only" a 20-point hit-ratio drop, which is why hit ratio is such a sensitive early-warning signal.
Trade-offs and pitfalls
A hit ratio number without context is misleading: 80 percent is excellent for a highly diverse, long-tail workload and mediocre for a workload with a small, stable, highly-repeated key set that should be closer to 99 percent. Always compare against the workload's own historical baseline and theoretical ceiling, not a generic industry number.
You need a distributed cache topology for very large embedding tables (hundreds of GB) to reduce remote datastore hits during inference. Compare sharded in-memory caches, SSD-backed caches, and hybrid approaches. Discuss eviction policies, cache warming, cold-starts, and how to ensure graceful fallbacks when items miss the cache.
Sample Answer
Direct answer
Caching for machine learning (ML) inference has to account for a dimension plain application caching does not: model version, since a cached prediction or embedding is only valid for the model version that produced it, and serving a stale model's output as if it were current can silently degrade prediction quality without looking like a technical failure at all.
Structured elaboration
- Embedding tables: very large embedding tables (hundreds of gigabytes) rarely fit fully in a fast in-memory tier; a sharded in-memory cache for the hottest embeddings, backed by an SSD-backed or remote-store fallback for the long tail, balances cost against latency, with eviction policy tuned to actual embedding access skew (typically also a heavily skewed distribution, similar to other hot-key patterns).
- Model-output/prediction caching: caching an inference result keyed by input (and model version) avoids recomputing identical predictions repeatedly, valuable when the same input recurs often (a popular item's recommendation score, for example) but must invalidate on model updates, since a new model version's prediction for the same input can differ from the old one, and serving the old cached value would silently mix outputs from two different models.
- Feature caching: caching precomputed features (not just final predictions) is useful when features are expensive to compute but shared across multiple models or requests; freshness requirements here are usually tied to how quickly the underlying signal (user behavior, real-time context) needs to be reflected in the feature value.
- Per-tenant customization: different consumers of the same caching layer (different product surfaces, different customers) may need different TTLs or eviction priorities for what is otherwise the same underlying cached data, requiring the cache key or a policy layer to account for tenant-specific freshness/policy needs.
- Cache-warming to prevent thundering herd on deployment: rolling out a new model version invalidates (or requires re-keying) every cached prediction tied to the old version at once; without proactive warming of the hottest inputs against the new model before full traffic cutover, this produces exactly the kind of stampede covered elsewhere in this topic, now triggered by a model deploy rather than a data change.
- Graceful fallbacks on a cache miss: for latency-sensitive inference paths, a miss should fall back to either a faster, simpler model, or a cached PREVIOUS prediction with an explicit staleness flag, rather than blocking the request on a full, expensive inference call if that would violate a latency budget.
Worked example
A recommendation model deployed as version 47 caches predictions keyed by (user_id, item_id, model_version=47); when version 48 deploys, ALL previously-cached predictions are naturally excluded by the version-scoped key (a cache miss, not stale data served incorrectly), and a proactive warming pass precomputes predictions for the highest-traffic user/item pairs against version 48 before full traffic cutover, avoiding a cold-cache stampede against the inference service at deploy time.
Trade-offs and pitfalls
Caching predictions WITHOUT including model version in the key is a serious, easy-to-miss correctness bug: it silently serves a mix of predictions from different model versions with no visible error, which is far harder to detect than an outright cache miss, since predictions from a slightly different model version still "look like" plausible outputs. Warming every possible input against a newly deployed model is usually infeasible; prioritize by traffic volume (the same hot-key-driven approach used elsewhere in this topic) rather than attempting exhaustive coverage.
Compare the cache-aside, read-through, write-through, and write-behind caching patterns. For each pattern describe: (a) how reads and writes flow between cache and data store, (b) a typical use case, and (c) the main advantage and drawback. Give one example service type where each pattern is a good fit.
Sample Answer
Direct answer
Cache-aside, read-through, write-through, and write-behind differ in who is responsible for populating the cache and when a write becomes durable: cache-aside puts that responsibility on the application, read-through/write-through push it into the caching layer itself, and write-behind trades immediate durability for write throughput.
Structured elaboration
- Cache-aside (lazy loading): on read, the application checks the cache; on a miss, it reads from the datastore and populates the cache itself. On write, the application writes to the datastore and either invalidates or updates the cache entry. The application owns all the logic; the cache is a dumb key-value store. This is the most common pattern because it fails gracefully (if the cache is down, reads just go straight to the datastore) and only caches what is actually requested.
- Read-through: functionally similar to cache-aside from the caller's perspective, but the cache library/layer itself knows how to fetch from the datastore on a miss, so the application only ever talks to the cache. This centralizes the fetch logic but requires a caching layer that supports it.
- Write-through: every write goes to the cache first (or simultaneously), and the cache synchronously writes through to the datastore before acknowledging. Reads are always fresh because the cache is never behind the datastore, at the cost of write latency (you pay for both writes on every request) and caching data that may never actually be read.
- Write-behind (write-back): writes go to the cache and are acknowledged immediately; the cache asynchronously flushes to the datastore in the background (often batched). This gives the best write throughput and latency, at the cost of a durability window: a crash between the acknowledged write and the flush can lose data unless the write queue itself is durable.
- Picking one, by use case: a product catalog with heavy reads and occasional updates fits cache-aside well (simple, only caches what's actually browsed). A durability-sensitive write path (a payments ledger) generally avoids write-behind's data-loss window and prefers write-through or a cache-aside pattern with synchronous invalidation.
Worked example
For a product catalog service: cache-aside is a strong default. On a product-detail read, check Redis; on miss, query the database and populate Redis with a time-to-live (TTL); on a price update, write to the database and then delete (or update) the cached entry so the next read repopulates it. This avoids caching the 90+ percent of the catalog nobody is currently browsing, unlike write-through, which would populate the cache for every single write regardless of read demand.
Trade-offs and pitfalls
Cache-aside has a well-known race: a read that misses, starts fetching from the datastore, and finishes AFTER a concurrent write has already invalidated the cache, can re-populate the cache with the now-stale value it fetched before the write. Write-through eliminates staleness but adds write latency and can cache "dead weight" (data nobody reads). Write-behind's throughput win is real but its durability trade-off must be an explicit decision, not a default; never use write-behind for data where losing the last few seconds of writes is unacceptable.
How do you decide whether to introduce a cache for a given service endpoint? Describe the signals and measurements you would collect, the tests you would run (load, latency, profiling), and the criteria that justify adding an in-process cache, a shared cache (Redis), or a CDN. Include considerations for cost, operational complexity, and correctness.
Sample Answer
Direct answer
Decide whether to add a cache by measuring the actual read pattern (how often the same value is requested, how expensive it is to produce, and how much staleness is tolerable), not by defaulting to caching every endpoint; a cache with a low repeat-read rate or zero staleness tolerance is a cost with no real benefit.
Structured elaboration
- Signals to collect: request rate for the same key/query (does the same data actually get read repeatedly, or is nearly every read unique), the cost of producing the value (a fast, cheap lookup gains little from caching even if repeated), and the data's staleness tolerance (how quickly must a change be visible).
- Tests to run: a load test comparing latency and backend load with and without a proposed cache, and a profile of the actual query/computation to confirm it is genuinely a meaningful cost worth caching against.
- Criteria for choosing a cache tier: an in-process cache fits data that is cheap to duplicate per instance and does not need cross-instance consistency; a shared cache (Redis) fits data that benefits from being consistent across instances or too large to duplicate per instance; a content delivery network (CDN) fits public, non-personalized content that benefits from being close to users geographically.
- Cost: weigh the infrastructure and operational cost of adding a caching layer (a new dependency to monitor, secure, and keep available) against the actual load/latency benefit measured above; a marginal benefit may not justify the added complexity.
- Operational complexity: caching adds invalidation logic, a new failure mode (cache unavailable), and another thing to monitor; these costs are real even when the caching decision is otherwise sound, and should be weighed explicitly.
- Correctness: if the data's staleness tolerance is effectively zero (a value that must always reflect the absolute latest state, with no acceptable delay), caching adds risk without benefit, since any caching mechanism introduces at least a small window of potential staleness.
Worked example
An endpoint returning a real-time stock quote, requested uniquely per symbol per user with essentially no repeat reads within any meaningful window, and requiring zero staleness tolerance: this fails on both the "does the same value get read repeatedly" test and the "can staleness be tolerated" test, making it a poor caching candidate regardless of how expensive the underlying computation is. Contrast with a product description, read thousands of times per hour by different users for the same handful of popular items, changing rarely: this passes both tests clearly.
Trade-offs and pitfalls
Caching by default, without measuring the actual read-repetition rate, either wastes cache capacity on data that gets no benefit or, worse, introduces a staleness risk on data that could not tolerate it; always start from measurement, not habit. The decision is not binary per endpoint; the same service can have some data that benefits enormously from caching and other data (even on the same page) that should never be cached, and treating the whole endpoint uniformly misses that nuance.
Unlock Full Question Bank
Get access to all 8 Caching Strategies and Distributed Caching interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.