Skip to solution
mediumSystem Design

How would you implement caching in a Node.js application?

1.2k views
01

Understand the problem

Question presented to candidate: "You cache a user's profile data to avoid a repeated database query, but the user updates their profile five minutes later. How does your cache avoid serving the old, now-wrong data forever?"

What a strong answer should cover:

  • Caching stores an expensive-to-compute or expensive-to-fetch result, keyed by its input, so a repeated request for the identical input can be served from memory instead of redoing the work — verified with real measured timing (an identical computation dropping from 104ms to 0ms) in the dedicated performance-techniques question.
  • 📌 The core problem the prompt's scenario raises, and its standard fix: a cache with no expiration or invalidation strategy will happily serve stale data forever. A TTL (time-to-live) is the simplest fix — verified directly: a cached value read correctly immediately after being set, and the identical key read again after its TTL had genuinely elapsed correctly returned nothing, forcing a fresh fetch.
  • Beyond a blind TTL, explicit invalidation (deleting or updating the cached entry the moment the underlying data actually changes — e.g. when the profile update itself is saved) is more precise than waiting out a TTL, at the cost of needing to remember to invalidate at every single write path that could make the cached value stale.
  • Where the cache lives matters for correctness at scale: an in-process cache (a plain Map) is fastest but is not shared across multiple processes/instances (covered in the dedicated clustering question, with real proof that each worker process has genuinely separate memory) — a shared external cache (Redis) is required when multiple processes/instances must see the same cached state consistently.
  • A precise answer distinguishes cache-aside (the application checks the cache, falls back to the source on a miss, then populates the cache — the pattern demonstrated directly here) from a write-through cache (updated proactively at write time, alongside the source of truth) — genuinely different strategies for keeping the cache correct, not interchangeable details.
  • The honest trade-off, stated explicitly: caching trades some staleness risk for speed — the right TTL/invalidation strategy depends entirely on how tolerable a temporarily-stale value actually is for that specific piece of data, which is a product/business decision as much as a technical one.

Clarifying questions expected:

  • "How tolerable is briefly-stale data for this specific value — seconds, minutes, or never?" — directly decides the TTL/invalidation strategy.
  • "Does this cache need to be consistent across multiple processes/instances, or is in-process sufficient?" — decides between a local Map and an external store like Redis.

Code / implementation expected: Yes — a real TTL-based cache, verified actually expiring a value after its real elapsed time, is the concrete, convincing demonstration of the prompt's exact staleness concern being handled correctly.

cachingperformanceredisoptimization
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js system-design interviews — assumes familiarity with the performance-techniques question's real cache-timing proof. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. Th

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

A real TTL-based cache: correct read before expiration, genuinely empty after the real TTL elapses
class TTLCache {
  constructor() { this.store = new Map(); }
  set(key, value, ttlMs) {
    this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
  }
  get(key) {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (Date.now() > entry.expiresAt) { this.store.delete(key); return undefined; }
    return entry.value;
  }
}

const cache = new TTLCache();
cache.set("user:1", { name: "Ada" }, 100); // 100ms TTL

console.log(cache.get("user:1")); // { name: 'Ada' } — immediately after set

setTimeout(() => {
  console.log(cache.get("user:1")); // undefined — genuinely expired, forces a fresh fetch
}, 150);
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 39 of 152 decoded in the Node.js track. One more won't hurt.

Back to track