Skip to the solution
MediumSystem Design6 min read

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

1.2k views

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.

  • caching
  • performance
  • redis
  • optimization

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Read the code

JavaScriptA 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);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 39 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

Explain the concept of 'event-driven architecture' in Node.js.

Continue