JavaScript WeakMap and WeakSet
In this chapter, we provide you with comprehensive information about useful means of storing data in JavaScript. Learn about WeakMap and Weakset here.
Introduction to JavaScript WeakMap and WeakSet
WeakMap and WeakSet are specialized versions of Map and Set. They look almost identical to use, but they differ in one decisive way: they hold their keys (or members) weakly. A weak reference does not stop the JavaScript engine from removing an object from memory. If the only thing pointing at an object is a WeakMap key or a WeakSet member, the object can still be garbage-collected.
That single property gives WeakMap and WeakSet their purpose: they let you associate extra data with an object without controlling that object's lifetime. When the object goes away, the associated data goes away with it — automatically, with no cleanup code.
This chapter covers the rules that set them apart, the trade-offs those rules impose, and the real situations where they are the right tool.
What "weak" means
In a regular Map, a key is a strong reference. As long as the Map exists, every key it holds is kept alive — even if no other part of your program still uses that key. That is a common source of memory leaks: you forget to delete an entry, and the key object lingers forever.
A WeakMap reverses this. The key is referenced weakly, so the engine is free to reclaim the key object as soon as nothing else refers to it. When that happens, the entry simply disappears from the WeakMap.
let visits = new WeakMap();
let user = { name: "Alice" };
visits.set(user, 10);
console.log(visits.get(user)); // 10
console.log(visits.has(user)); // true
user = null; // the object is now unreachable elsewhere
// The WeakMap entry becomes eligible for garbage collection.
// You cannot observe exactly when it is removed.WeakMap
A WeakMap is a collection of key/value pairs where the keys must be objects and the values may be anything.
Keys must be objects
Primitive values (strings, numbers, booleans, symbol, null, undefined) cannot be keys. The reason follows from the design: primitives are not garbage-collected the way objects are, so "weakly holding" them is meaningless. Trying to use one throws a TypeError.
let wm = new WeakMap();
wm.set("a string", 1); // TypeError: Invalid value used as weak map keyAvailable methods
A WeakMap supports only four operations:
set(key, value)— store a value under an object key.get(key)— read the value, orundefinedif absent.has(key)— check whether a key exists.delete(key)— remove an entry.
let wm = new WeakMap();
let key = { id: 1 };
wm.set(key, "data");
console.log(wm.has(key)); // true
console.log(wm.get(key)); // "data"
wm.delete(key);
console.log(wm.has(key)); // falseNo iteration and no size
There is no size property, no clear(), and no way to loop over a WeakMap (no keys(), values(), entries(), or forEach). This is not an oversight. Because entries can vanish at any moment when the garbage collector runs, the contents are non-deterministic — exposing them would let your code observe GC timing, which the language deliberately hides.
let wm = new WeakMap();
console.log("size" in wm); // false
console.log(wm[Symbol.iterator]); // undefinedIf you need to count entries, iterate, or store primitive keys, use a regular Map instead.
WeakSet
A WeakSet is the Set equivalent: a collection of unique objects that are held weakly. Like WeakMap, its members must be objects, and it offers no iteration or size.
let visited = new WeakSet();
let a = { id: 1 };
let b = { id: 2 };
visited.add(a);
console.log(visited.has(a)); // true
console.log(visited.has(b)); // false
visited.delete(a);
console.log(visited.has(a)); // falseIts full API is just add(value), has(value), and delete(value).
Practical use cases
Caching and memoization
Cache the result of an expensive computation keyed by the object it relates to. Because the key is weak, the cache never keeps a no-longer-needed object alive.
const cache = new WeakMap();
function process(obj) {
if (cache.has(obj)) {
return cache.get(obj); // reuse the cached result
}
const result = obj.value * 2; // pretend this is expensive
cache.set(obj, result);
return result;
}
let data = { value: 21 };
console.log(process(data)); // 42 (computed)
console.log(process(data)); // 42 (from cache)Private per-object data
Store data that belongs to an object without adding a property to the object itself (which anyone could read or overwrite). This is a classic way to emulate truly private fields, related to how methods bind to objects via this.
const balances = new WeakMap();
class Account {
constructor(amount) {
balances.set(this, amount); // private to this module
}
deposit(n) {
balances.set(this, balances.get(this) + n);
}
get balance() {
return balances.get(this);
}
}
const acc = new Account(100);
acc.deposit(50);
console.log(acc.balance); // 150
// There is no `amount` property on `acc` itself to tamper with.When the Account instance is no longer referenced, its balance entry is collected automatically.
Metadata for DOM nodes
A frequent leak in long-running pages is keeping a Map of DOM-node metadata after the nodes are removed from the document. With a WeakSet/WeakMap, once a node is detached and dereferenced, its metadata is reclaimed too.
const seen = new WeakSet();
function markVisited(node) {
if (seen.has(node)) return false; // already processed
seen.add(node);
return true;
}
// When the node leaves the DOM and nothing else holds it,
// it disappears from `seen` without manual cleanup.WeakMap/WeakSet vs Map/Set
| Capability | Map / Set | WeakMap / WeakSet |
|---|---|---|
| Keys / members | Any value | Objects only |
| Reference strength | Strong (keeps keys alive) | Weak (allows GC) |
size, clear() | Yes | No |
Iteration (forEach, keys…) | Yes | No |
| Best for | General collections | Object-associated data with automatic cleanup |
Choose the weak variant only when you specifically want the engine to manage cleanup for you and you do not need to enumerate the contents. For everything else, reach for the regular Map and Set.