Step 4 — Side Tables, Caches, and the Limits of WeakMap

Goal

Measure the most common accidental cache leak, and establish when WeakMap is not the answer.

Prerequisites

  • Steps 1–3 complete

Predict first

2,000 DOM nodes are created, used as keys in a module-level side table with per-node metadata, then detached and dereferenced. Two variants, identical except:

const store = new Map();       // vs
const store = new WeakMap();

Predict retained heap and surviving detached-node count for each.

Run

npm run weakmap

Expected output:

  side table | retained JS heap | detached nodes still alive
  -----------|------------------|---------------------------
  Map        |          38.34MB |                      2000
  WeakMap    |           0.09MB |                         0

What just happened

A Map holds its keys strongly. Every node the map has ever seen is immortal, along with the metadata hanging off it. The nodes are unreachable from the document and from application code, and still not collectable — the map is the only thing keeping 38 MB alive.

This is the shape of most accidental caches: a module-level Map, Set, registry, or id → object index that nothing ever deletes from.

A cache without an eviction policy is a leak with better branding.

But WeakMap is not a default. Five limits worth knowing before reaching for it:

  1. It weakens the key, not the value. wm.set(node, { node }) retains everything, because the value's reference to the key is strong. Easy to write, hard to see.
  2. Not enumerable. No .size, no iteration, no dump during an incident. You have traded a visible leak for an invisible cache.
  3. Object keys only. Anything keyed by an id string cannot use it.
  4. Non-deterministic collection. You cannot write a test asserting an entry was freed — only that growth is bounded, which is a weaker claim.
  5. It implements one policy: "when the key dies". If entries should expire on logout, on navigation, or after 30 seconds, WeakMap implements none of that.

The decision rule:

The entry should die when…Use
its key object diesWeakMap
a fixed time passesTTL cache
memory is the binding constraintLRU
a specific event occurs (logout, tenant switch)explicit clear-on-event

Point 5 crosses into security: cached user data surviving a logout is a privacy finding, not a memory one, and WeakMap would not have fixed it. Picked up in fe-25.

Checkpoint

docs/verification.md Checkpoint 4.