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.
Compare TTL (time-to-live) based expiration with explicit eviction/purge. Provide examples of use-cases where TTL is sufficient and where explicit invalidation is necessary. Also list drawbacks of using long TTL values.
Sample Answer
Direct answer
Time-to-live (TTL) expiration is a passive, self-healing bound on staleness that requires no extra machinery; explicit invalidation (purge, versioned keys, or event-driven invalidation) actively corrects the cache the moment the source changes, at the cost of needing a reliable signal that a change happened.
Structured elaboration
- When TTL is sufficient: content where a bounded staleness window is genuinely acceptable (a homepage banner, a "trending now" list, most analytics dashboards); TTL requires no coordination and self-heals even if a write is missed entirely, because the entry expires on schedule regardless.
- When explicit invalidation is necessary: content where staleness has real consequences (permission revocation, inventory during checkout, a price correction that must take effect immediately); waiting out even a short TTL is unacceptable when correctness, not just freshness, is on the line.
- Versioned keys as a form of explicit invalidation: rather than deleting the old entry, bump a version token that is part of the key; the old version simply becomes unreachable and ages out naturally, avoiding races where a delete arrives before or after the write it corresponds to.
- Event-driven invalidation: a write publishes an event (directly or via change data capture, CDC) that consumers use to purge or update affected cache entries; this gets close to real-time correction but adds a dependency on the message delivery being reliable.
- A hybrid approach: use a moderate TTL as a safety net (so a lost invalidation event self-heals within a bounded window) combined with explicit invalidation for the fast path; this is the most common production pattern because it gets the responsiveness of explicit invalidation with the resilience of TTL.
Worked example
A user's permission level changes from "member" to "banned." Relying purely on a 5-minute TTL means the banned user could still access restricted content for up to 5 minutes; that is not acceptable, so this write triggers explicit invalidation of the user's cached permission entry, with a short TTL (e.g., 60 seconds) still applied as a safety net in case the invalidation event is somehow lost.
Trade-offs and pitfalls
Long TTLs used purely for "simplicity" on data that actually needs freshness is one of the most common production incidents in caching (a stale price, a stale permission, a stale feature flag reaching users for far longer than intended); size the TTL to the actual staleness tolerance of the data, not to whatever is convenient to implement. Relying on explicit invalidation alone, with no TTL backstop, means any lost or delayed invalidation event becomes unbounded staleness with no self-correction.
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 cache placement options: client-side, CDN/edge, reverse-proxy (e.g., Varnish), application-level in-memory (e.g., Redis/Memcached), and database-side (materialized views or DB-level caching). For each option describe pros, cons, typical use cases, security/privacy considerations, and how TTLs and invalidation differ by placement.
Sample Answer
Direct answer
Cache placement is a ladder from "closest to the user, cheapest, hardest to invalidate precisely" to "closest to the source of truth, most expensive per request, easiest to keep correct": client-side, content delivery network (CDN)/edge, reverse proxy, application in-memory, and database-side.
Structured elaboration
- Client-side: browser cache, local storage, or a mobile app's local store. Zero network cost on a hit, but you have essentially no control once data leaves your servers; invalidation means waiting out a time-to-live (TTL) or changing a versioned URL.
- CDN/edge: shared across all users near a given geography, ideal for static or long-TTL, non-personalized content. Purge/invalidation is slower (can take seconds to propagate globally) and typically coarser-grained than a server-side cache.
- Reverse proxy (e.g., Varnish): sits in front of your application servers, caching full HTTP responses; good for reducing application-server load for cacheable pages without needing application code changes, but still shared and public unless carefully scoped per-user.
- Application-level in-memory (Redis/Memcached, or in-process): shared across your own service's instances (Redis/Memcached) or private to one instance (in-process); this is where most business-logic caching happens, because you have full control over invalidation and can cache personalized data safely.
- Database-side (materialized views, query result caching): closest to the source of truth, so almost always the most consistent option, at the cost of doing the least to reduce load on the database itself.
- Redis vs. CDN by use case: for static assets, a CDN wins outright (no reason to burn application-tier memory on data that never changes per-request). For personalized HTML fragments, a CDN only works with edge compute; otherwise application-tier Redis is the safe default. For frequently-read configuration flags, an in-process or small Redis cache with a short TTL beats a CDN, since the data is tiny and needs low latency, not global edge distribution. For large objects with varying TTLs (e.g., images), a CDN with per-object cache-control headers is the natural fit.
Worked example
A product page has a static hero image (CDN, long TTL, content-hashed URL so invalidation is just a new URL), a shared "similar products" block computed the same for all users (reverse proxy or application cache, medium TTL), and a personalized "recently viewed" section (application-level cache keyed per user, short TTL, never placed at a shared CDN/proxy layer).
Trade-offs and pitfalls
Placing personalized content at a shared caching layer (CDN or reverse proxy) without per-user cache keys is a serious privacy bug, not just a staleness inconvenience; one user's private data can be served to another. The further a cache sits from the source of truth, the cheaper it is per request and the harder it is to invalidate precisely; choose the placement based on how quickly and precisely that specific data needs to be corrected on write.
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.
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.