Skip to content
JavaScript js collections 4 min read

WeakMap & WeakSet

WeakMap and WeakSet are specialized collections that hold weak references to the objects they store. Unlike Map and Set, they don’t prevent their keys (or members) from being garbage-collected: once nothing else in your program references an object, the engine is free to reclaim it and quietly drop the corresponding entry. This makes them ideal for associating private data or cached metadata with objects without risking memory leaks.

Why weak references matter

A normal Map keeps a strong reference to every key. As long as the Map exists, its keys can never be garbage-collected — even if the rest of your code has forgotten about them. That’s perfect when you want stable, iterable storage, but it’s a hazard when you’re attaching data to objects whose lifetime you don’t control (DOM nodes, request objects, user-supplied instances).

WeakMap solves this by not counting toward an object’s reachability. If the only reference to an object is its presence as a WeakMap key, that object becomes eligible for collection, and the engine removes the entry automatically.

let user = { name: "Ada" };

const lastSeen = new WeakMap();
lastSeen.set(user, Date.now());

console.log(lastSeen.has(user)); // true

user = null; // the object is now unreachable elsewhere
// At some later point the GC may reclaim it,
// and the WeakMap entry disappears with it — no manual cleanup.

Output:

true

Object-only keys

Because weak referencing only makes sense for things the garbage collector tracks, WeakMap keys and WeakSet members must be objects (or non-registered symbols, since ES2023). Primitives like strings and numbers are interned or value-typed and would throw.

const wm = new WeakMap();

wm.set({}, "ok");            // fine — object key
wm.set([], "also ok");       // fine — arrays are objects

try {
  wm.set("string-key", 1);   // TypeError: Invalid value used as weak map key
} catch (err) {
  console.log(err.name);
}

Output:

TypeError

Not iterable, not measurable

You cannot loop over a WeakMap or WeakSet, read its size, or clear it wholesale. There’s a good reason: entries can vanish at unpredictable times during garbage collection, so exposing iteration would make program behavior non-deterministic. The API is deliberately tiny.

CapabilityWeakMapWeakSetMap / Set
Allowed keys/valuesobjects (+ symbols)objects (+ symbols)any value
size propertynonoyes
Iterable / spreadablenonoyes
forEachnonoyes
clear()nonoyes
Holds entries stronglynonoyes

The available methods are get, set, has, and delete for WeakMap; and add, has, and delete for WeakSet.

Because you can’t enumerate them, treat WeakMap/WeakSet as write-and-look-up stores keyed by an object you already hold — never as a place to “list everything you’ve stored.”

Use case: truly private data

A classic pattern is storing per-instance private state in a module-scoped WeakMap. The data is keyed by the instance, inaccessible from outside the module, and cleaned up automatically when the instance is collected.

const privates = new WeakMap();

class BankAccount {
  constructor(balance) {
    privates.set(this, { balance });
  }

  deposit(amount) {
    privates.get(this).balance += amount;
    return this;
  }

  get balance() {
    return privates.get(this).balance;
  }
}

const acct = new BankAccount(100);
acct.deposit(50);
console.log(acct.balance);

Output:

150

Modern code often reaches for native #private class fields instead, but a WeakMap still shines when you need to attach private data to objects you didn’t create — like third-party instances or DOM elements.

Use case: leak-free caches and metadata

WeakMap is the natural backing store for a memoization cache or for tagging objects with computed metadata. When the underlying object goes away, its cached result goes with it, so the cache never grows unbounded.

const sizeCache = new WeakMap();

function getNodeArea(el) {
  if (sizeCache.has(el)) {
    return sizeCache.get(el);
  }
  const rect = el.getBoundingClientRect();
  const area = rect.width * rect.height;
  sizeCache.set(el, area);
  return area;
}

WeakSet: tracking membership

WeakSet stores a collection of unique objects and answers a single question: “have I seen this object?” It’s perfect for marking objects as visited or processed without keeping them alive.

const processed = new WeakSet();

function handle(task) {
  if (processed.has(task)) {
    return "skipped (already done)";
  }
  processed.add(task);
  return "processed";
}

const job = { id: 1 };
console.log(handle(job));
console.log(handle(job));

Output:

processed
skipped (already done)

Choosing between weak and strong collections

Reach for Map/Set when you need to iterate, count entries, use primitive keys, or deliberately keep values alive. Reach for WeakMap/WeakSet when you’re decorating externally-owned objects with side data and want the cleanup to be automatic. If you ever find yourself wanting to list a WeakMap’s contents, that’s a strong sign you actually wanted a Map.

Best Practices

  • Use WeakMap to attach private state or caches to objects whose lifetime you don’t own, so entries clean up themselves.
  • Remember keys must be objects (or non-registered symbols) — guard against primitive keys if input is untrusted.
  • Don’t rely on garbage collection timing; entries may persist for a while. Weak collections manage memory, not deterministic deletion.
  • Prefer native #private fields for first-party classes; choose WeakMap for third-party or DOM objects.
  • Never try to iterate or read size — design around lookups by an object you already hold.
  • For deterministic teardown that must run when an object is collected, look at FinalizationRegistry and WeakRef, which complement these collections.
Last updated June 1, 2026
Was this helpful?