Sikau

System design · Day 3 · about 40 min

Caching

An LRU cache with expiry, cache-aside loading and hit rates.

Don't ask the database twice

Reading memory takes nanoseconds; a database query takes milliseconds. A cache keeps recent answers close by. It must stay small, so it evicts the LRU (least recently used) entry when full — and entries expire after a TTL so stale data doesn't live forever.

LRU with a Map

function createCache({ capacity, ttl = Infinity, now = () => Date.now() }) {
  const map = new Map(); // a Map remembers insertion order: oldest first
  let hits = 0;
  let misses = 0;
  return {
    get(key) {
      const entry = map.get(key);
      if (!entry || now() >= entry.expires) {
        if (entry) map.delete(key); // expired
        misses++;
        return undefined;
      }
      map.delete(key); // move to the newest end
      map.set(key, entry);
      hits++;
      return entry.value;
    },
    set(key, value) {
      map.delete(key);
      map.set(key, { value, expires: now() + ttl });
      if (map.size > capacity) map.delete(map.keys().next().value); // evict least recently used
    },
    async getOrLoad(key, loader) {
      const cached = this.get(key);
      if (cached !== undefined) return cached;
      const value = await loader(key); // cache-aside: miss → load → store
      this.set(key, value);
      return value;
    },
    stats() {
      const total = hits + misses;
      return { hits, misses, hitRate: total ? hits / total : 0 };
    },
    size: () => map.size,
  };
}

The rest of this lesson

  • Cache-asideexplain
  • Choosing TTLs and measuringexplain
  • Cache-aside readorder
  • Hit ratepredict
  • LRU + TTLwrite code
  • Badge quizquiz
  • What you learnedrecap

Continue this lesson free — run real code in your browser

Continue with Google

See all 8 lessons of System design