WeakMap

Stores key/value pairs whose object keys do not stop those objects from being garbage collected.

Signature
new WeakMap()
new WeakMap(iterable)
  • Added in JavaScript ES2015

Parameters

ParameterTypeDescription
$iterableoptionalIterable<[key, value]>= undefinedAn iterable of two-element array-like objects, each a key/value pair, used to populate the new WeakMap. null or undefined is treated as no argument.

Return value

WeakMapA new WeakMap object holding weakly-referenced keys. It can only be constructed with new; calling it as a function throws.

Errors

set(), get(), has() and delete() require a key that is an object or a non-registered symbol; set() throws a TypeError otherwise (get(), has() and delete() simply return undefined or false). Calling WeakMap without new throws a TypeError.

Changelog

  • PHP ES2015Introduced. Baseline widely available since July 2015.
  • PHP Later revisionNon-registered symbols became legal keys alongside objects. Symbols from the global registry, i.e. Symbol.for("x"), are still rejected because they are never collectable.

A WeakMap is a side table: it associates data with an object without touching that object and without keeping it alive. If the key becomes unreachable everywhere else, the entry can be reclaimed. That single property is the reason to use it — a plain Map keyed by objects is a leak, because the map itself is a strong reference that keeps every key in memory for as long as the map lives.

When to reach for it

Use it for per-object metadata you do not control the lifetime of: caching an expensive derivation for a value someone else owns, storing private state for instances of a class, marking objects as already processed, or attaching bookkeeping to nodes in a graph you may later discard.

What trips people up

The API is deliberately tiny — get, set, has, delete. There is no size, no forEach, no iteration and no clear(), because exposing the contents would make garbage collection timing observable. If you need to list entries, you need a Map.

Keys are matched by identity, not by contents, so a structurally identical object literal will never find an existing entry. And primitive keys are rejected outright: strings and numbers throw a TypeError on set().

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Cache a parsed or computed result keyed by the object it was derived from
  • Hold private per-instance state that subclasses and JSON serialization cannot see
  • Mark objects as already visited while walking a cyclic graph
  • Attach listener bookkeeping to objects owned by another library

Frequently asked questions

Why can I not loop over a WeakMap or read its size?

Because enumeration would let you observe exactly when the garbage collector reclaimed a key, which the language deliberately keeps unobservable. If you need to iterate, you need a Map and you accept that it retains its keys.

When should I use WeakMap instead of Map?

Use WeakMap when the keys are objects whose lifetime you do not control and the entry is meaningless once the key is gone — caches, private state, per-object metadata. Use Map when keys are primitives, or when you need iteration, size, or entries that outlive their keys.

Can a WeakMap key be a string or number?

No — keys must be objects or non-registered symbols, and set() throws a TypeError for anything else. Primitives are not collectable, so weakness would be meaningless for them.

More examples

Private instance state

The balance lives in the WeakMap rather than on the instance, so it survives every method call but never appears in Object.keys() or JSON output. When the account object is dropped, its entry becomes collectable with it.

Example · javascript
const balances = new WeakMap();

class Account {
  constructor(start) {
    balances.set(this, start);
  }
  deposit(n) {
    balances.set(this, balances.get(this) + n);
    return this;
  }
  get balance() {
    return balances.get(this);
  }
}

const a = new Account(100).deposit(50);
console.log(a.balance); // 150
console.log(Object.keys(a), JSON.stringify(a)); // [] {}

Memoizing a derivation per object

The second call hits the cache, so the counter stays at one. Because the cache is weak, it never becomes the reason payload stays in memory — a Map here would pin every object ever passed in.

Example · javascript
const sizes = new WeakMap();
let computations = 0;

function weight(obj) {
  if (!sizes.has(obj)) {
    computations++;
    sizes.set(obj, JSON.stringify(obj).length);
  }
  return sizes.get(obj);
}

const payload = { a: 1, b: [2, 3] };
console.log(weight(payload), weight(payload)); // 17 17
console.log("computed once:", computations === 1); // computed once: true

Seeding from an iterable and using symbol keys

The constructor accepts any iterable of [key, value] pairs. Unique symbols are valid keys, but a symbol from the global registry is permanently reachable and so is rejected — this line throws in engines that support symbol keys at all.

Example · javascript
const k1 = { name: "a" };
const k2 = Symbol("local");
const wm = new WeakMap([
  [k1, 1],
  [k2, 2],
]);

console.log(wm.get(k1), wm.get(k2)); // 1 2

try {
  wm.set(Symbol.for("global"), 3); // registry symbols never die
} catch (err) {
  console.log(err.name); // "TypeError"
}

Discussion

  • Be the first to comment on this lesson.