skip to content

Why do JavaScript's WeakMap and WeakSet expose no size property and no way to iterate their contents?

level: middleimportance: should knowfreq 46%

answer

  1. contents change without the program acting
  2. collection timing is unspecified
  3. observation would make it observable
  4. engine-dependent output is unacceptable
  5. only lookups by a key you hold

basics

~20 s

Because their contents change whenever the garbage collector runs. Counting or enumerating them would make collection timing — an unspecified implementation detail that varies by engine and run — observable to programs, so the language exposes no way to look.

solid answer

~50 s

The entries in a `WeakMap` disappear on their own, at a moment nobody specifies: some time after the key stops being reachable, whenever the engine happens to collect. If `size` or `keys()` existed, a program could poll them and learn exactly when a collection happened, so identical code would produce different output on different engines, different heap sizes, or the same machine twice. That is behaviour the specification refuses to standardise, so it removes the observation instead: no `size`, no `forEach`, no `keys`/`values`/`entries`, no `clear()`, and no `Symbol.iterator`, which is why spreading a WeakMap throws a `TypeError`. It also has a security dimension — enumeration would leak the existence of objects you were handed but should not be able to survey. The practical consequence is that a WeakMap only answers questions about a key you are already holding: `get`, `set`, `has`, `delete`.

go deeper

for a junior

Remember the shape of the API: WeakMap gives you get, set, has and delete and nothing else, and it cannot be spread or looped over.

for a middle

Explain the causation — entries vanish on the collector's schedule, so any count or enumeration would expose unspecified GC timing and make identical code behave differently across engines.

for a senior

Recognise the design signal in review: a requirement to count, flush or report on the collection means a strong Map plus an explicit eviction policy, not a weak structure bent into shape.

for a principal

Be ready to defend the trade the language made — unobservable internals in exchange for engines being free to change their collectors — and apply the same reasoning to your own APIs when deciding what internal state to expose.

## The rule and the reason `WeakMap` and `WeakSet` have four and three methods respectively — `get`/`set`/`has`/`delete` and `add`/`has`/`delete`. There is no `size`, no `forEach`, no `keys()`, `values()` or `entries()`, no `clear()`, and neither type implements `Symbol.iterator`: ```js const wm = new WeakMap(); wm.size; // undefined wm.clear; // undefined [...wm]; // TypeError: wm is not iterable ``` The reason is that the contents are not under the program's control. An entry survives exactly as long as its key object is reachable; after that, the engine may reclaim it at any moment — during the next minor collection, after a major one, or not at all before the process exits. ## Non-determinism is the core issue Garbage collection timing is deliberately unspecified in ECMAScript. Engines differ (generational, incremental, concurrent), and even a single engine's behaviour depends on allocation pressure, heap limits, and what else the process is doing. Nothing in the language guarantees that an unreachable object is collected by any particular point. Now imagine `weakMap.size` existed: ```js let key = {}; const wm = new WeakMap([[key, 'v']]); key = null; setTimeout(() => console.log(wm.size), 1000); // 0 or 1? ``` The answer would depend on whether a collection happened to run in that second. The same script would print `1` on one engine and `0` on another, or differ between two runs on the same machine. Programs would start depending on it — and any future GC improvement would then be a breaking change. By providing no observation point, the specification keeps collection timing an implementation detail that can never be depended upon, which is precisely what lets engines keep evolving their collectors. ## The information-hiding dimension There is a second motivation. Weak collections are commonly used to hold objects handed in from elsewhere — third-party instances, host objects, values crossing a library boundary. Enumeration would let anyone holding the collection survey those objects, and would let one piece of code learn about objects it never received a reference to. Non-enumerability keeps a WeakMap a pure lookup keyed by something you already possess, which is what makes it usable as a private-state store: without the key object, the entry is unreachable in every sense. ## What you can and cannot build The consequence is practical. You cannot: - count live entries, or report cache occupancy; - iterate to evict, expire, or flush entries in bulk (`clear()` is absent — drop the whole map and allocate a new one instead); - serialise a WeakMap, since `JSON.stringify(new WeakMap())` yields `{}` with no way to recover the contents; - write a test that asserts an entry was collected. You can only ask about a key in hand: ```js const seen = new WeakSet(); function visit(node) { if (seen.has(node)) return; // cycle guard seen.add(node); // ... } ``` If a requirement demands counting or enumeration, that is the signal you need a `Map` or `Set` plus an explicit eviction policy — a size cap, a TTL, or manual `delete` calls at a known lifecycle point — and you take responsibility for the lifetime that the weak structure would otherwise have handled for you. ## The related APIs that do let you observe — carefully ES2021 added `WeakRef` and `FinalizationRegistry`, which get closer to the collector: a `WeakRef` gives a weak reference to a single object whose `deref()` returns either the object or `undefined`, and a `FinalizationRegistry` invokes a callback some time after an object is collected. Even these come with heavy specification warnings: cleanup callbacks may never run, may run in any order, and their timing is not guaranteed, so correctness must never depend on them. They exist for opportunistic cleanup of external resources, not as an escape hatch for enumerating a WeakMap. ## How to say it in an interview The short version is one sentence: the API is small because the contents are non-deterministic, and exposing them would make garbage-collection timing observable. The follow-up that shows depth is naming the alternative — when you genuinely need counting or bulk eviction, you have chosen the wrong structure and should use a strong collection with an explicit lifecycle.

  • If you need to flush every entry from a WeakMap, what do you do without clear()?
    Replace the map: assign a fresh `new WeakMap()` to the variable and let the old one become garbage along with all its entries. If several modules share the reference, that will not work — which is a sign the data wanted a `Map` with explicit lifecycle management instead.
  • Doesn't WeakRef already let you observe collection, undermining this argument?
    Only partially, and with strong caveats. `WeakRef.prototype.deref()` returning `undefined` does reveal that an object went away, which is why the specification warns that programs must not depend on it and why engines keep a value alive for the rest of the current job once deref'd. The design deliberately makes it awkward rather than routine, whereas `size` would make it trivial.
  • How would you test that a WeakMap entry was actually released?
    You largely cannot at the language level — there is no portable force-GC and no way to enumerate. In practice you verify with heap snapshots in a profiler, or you run the engine with a debug flag that exposes collection (for example a Node build started with the flag that exposes `global.gc`) — a diagnostic technique, never something an application asserts on.

saying these in an interview costs you the question

  • Says size was simply forgotten or is coming later
  • Claims you can iterate with Object.keys or JSON.stringify
  • Thinks entries are removed synchronously on delete of the key variable
  • Suggests polling size to detect collection
  • Believes clear() exists on WeakMap

context