skip to content

In JavaScript, how does a WeakMap differ from a Map, and what values are allowed as WeakMap keys?

level: juniorimportance: must knowfreq 68%

answer

  1. one holds keys strongly, one does not
  2. reachability decides whether the entry survives
  3. objects only — primitives rejected
  4. no size, no iteration, no clear
  5. get, set, has, delete and nothing more

basics

~20 s

A WeakMap holds its keys weakly, so an entry becomes garbage-collectable as soon as nothing else references the key object. Keys must be objects (or unregistered symbols), and the API is only get, set, has and delete — no size, no iteration.

solid answer

~50 s

A `Map` keeps a strong reference to every key, so anything you put in it stays alive as long as the map does. A `WeakMap` references its keys weakly: once no other reachable code holds the key object, the engine is free to drop that key/value pair. That makes `WeakMap` the right structure for per-object metadata and caches that must not outlive the objects they describe. The trade-off is a much smaller surface. Keys must be objects — passing a string or number throws a `TypeError` (`new WeakMap().set('a', 1)` fails); since ES2023 unregistered symbols are accepted too, but `Symbol.for('x')` is not. There is no `size`, no `keys()`/`values()`/`entries()`, no `forEach`, no `clear()`, and a WeakMap is not iterable, because the contents depend on garbage-collection timing you are not allowed to observe. You get exactly `get`, `set`, `has` and `delete`.

code

javascript · 16 lines
javascript
const wm = new WeakMap();
let key = { id: 1 };

wm.set(key, { hits: 0 });
console.log(wm.has(key), wm.get(key)); // true { hits: 0 }

try {
  wm.set('primitive', 1);
} catch (e) {
  console.log(e.constructor.name); // TypeError
}

console.log(wm.size);              // undefined
console.log(typeof wm.clear);      // undefined

key = null; // entry is now eligible for collection

go deeper

for a junior

Be able to state plainly that WeakMap keys must be objects, that the entry can vanish once nothing else references the key, and that there is no size or iteration.

for a middle

Explain the mechanics in reachability terms: the map's reference to a key does not count as a reference, values are reachable only through their key, and the missing API follows from GC timing being unobservable.

for a senior

Show when the choice actually matters in production — annotating objects with lifetimes you do not own versus registries you need to enumerate — and be ready to say what WeakMap does not fix.

for a principal

Frame it as an ownership decision: who owns the lifetime of the keyed object, and whether the subsystem's bookkeeping is allowed to extend that lifetime. That choice, not the API, is what keeps long-running processes flat.

## What "weak" actually means A JavaScript engine reclaims an object when it is no longer *reachable* — when there is no chain of references from a root (globals, the call stack, live closures) to it. A normal `Map` participates in that graph: the map references its keys and values strongly, so while the map is alive, every key inside it is alive too. That is the whole leak: a long-lived registry keyed by short-lived objects pins them forever. `WeakMap` breaks that edge. Its reference to a key does not count towards reachability. If the only remaining reference to some object is its presence as a WeakMap key, the object is considered unreachable and both the key and its associated value become collectable. ```js let node = { id: 1 }; const meta = new WeakMap(); meta.set(node, { visits: 0 }); node = null; // the only strong reference is gone // the entry in `meta` is now eligible for collection ``` Values, by contrast, are held strongly *through the key*: the value stays alive exactly as long as its key does. ## Which keys are allowed Weak references only make sense for things with identity and a lifetime. Primitives have neither — `42` is not an object the collector can reclaim, and every occurrence of `42` is the same value — so `WeakMap` rejects them: ```js new WeakMap().set('a', 1); // TypeError: Invalid value used as weak map key ``` ES2023 added *unregistered* symbols (`Symbol('id')`) as legal keys, because each one is a unique, collectable value. Symbols from the global registry (`Symbol.for('id')`) are still rejected: the registry itself keeps them alive forever, so weakness would be a lie. `WeakSet` follows the same rule for its members, exposing `add`, `has` and `delete`. ## The deliberately small API `Map` gives you `size`, `forEach`, `keys()`, `values()`, `entries()`, `clear()`, and iterability via `Symbol.iterator`. `WeakMap` gives you none of those — `[...weakMap]` throws because it is not iterable, and `weakMap.size` is simply `undefined`. This is not an oversight. Anything that enumerated or counted the contents would let a program observe when the garbage collector ran, turning collection timing — an implementation detail that varies by engine, heap pressure and even run — into observable program behaviour. The language refuses to expose it, so the only questions you can ask are about a key you already hold. ## Constructing and using one The constructor optionally takes an iterable of pairs, like `Map`: ```js const a = {}, b = {}; const wm = new WeakMap([[a, 'first'], [b, 'second']]); wm.has(a); // true wm.get(b); // 'second' wm.delete(a); // true wm.get(a); // undefined ``` `get` on an absent key returns `undefined`, which is indistinguishable from a stored `undefined` — use `has` when that distinction matters. ## When to choose which Reach for `Map` when the collection *is* the data: you need to count it, iterate it, serialise it, or key it by strings, numbers, or anything primitive. Reach for `WeakMap` when the collection is *about* other data — annotations, caches, private state, "already processed" bookkeeping — and the owning objects have their own lifetime that you do not control. A useful test: ask whether you would ever want to walk the collection. If the answer is yes, weak references are wrong for the job; if the answer is "no, I only ever look things up by an object I am already holding", `WeakMap` is the natural fit and removes an entire class of leak. ## Traps worth remembering The weakness applies to the *key*, not the value. If a value strongly references some huge structure, that structure stays alive as long as the key does. The WeakMap itself also has to be reachable to matter at all — if the map is garbage, everything inside it goes with it, which is fine but occasionally surprising when people expect the entries to survive. Finally, weakness is a *permission*, not a schedule: the engine may collect the entry at any point after the key becomes unreachable, or not for a long time. You cannot write code that depends on the entry having disappeared by a particular moment, and there is no portable way to force collection.

  • Are the values in a WeakMap also held weakly?
    No — values are held strongly, but only reachable through their key. While the key object is alive, its value is alive too, so parking a large buffer as a value keeps that buffer in memory for the key's whole lifetime. Once the key becomes unreachable, the key/value pair goes together.
  • Why is Symbol.for('id') rejected as a WeakMap key when Symbol('id') is accepted?
    Registered symbols live in a global registry that keeps them reachable for the life of the realm, so they can never be collected — storing one weakly would be meaningless. An unregistered symbol has a normal lifetime and identity, so it behaves like an object key and is allowed from ES2023 onward.
  • What happens if you spread a WeakMap or pass it to for...of?
    It throws a TypeError, because WeakMap has no Symbol.iterator method. There is no supported way to enumerate its contents — the only reads are `get` and `has` with a key you already hold. If you need to walk the entries, you needed a Map.

A Map is a guest list that keeps everyone in the building; a WeakMap is a coat-check tag that quietly disappears the moment its owner walks out.

saying these in an interview costs you the question

  • Says WeakMap accepts string keys like Map
  • Claims the entry is removed immediately when the key is nulled
  • Thinks WeakMap has size or forEach
  • Believes values are held weakly too
  • Says WeakMap is just a faster or smaller Map

context