Understanding WeakMap and WeakSet in JavaScript
WeakMap and WeakSet are special collection types in JavaScript that hold objects weakly. A weakmap stores key-value pairs where the key must be an object; a weakset stores a collection of unique objects. The garbage collector can clean up entries when those objects are no longer referenced elsewhere in your program, making weakmap and weakset ideal for memory-sensitive association and tracking tasks.
What makes WeakMap and WeakSet different?
Regular Maps and Sets hold strong references to their keys. An object used as a Map key won’t be garbage collected as long as the Map exists, even if nothing else references it. WeakMap and WeakSet solve this by holding weak references.
The trade-off: you cannot iterate over WeakMap or WeakSet, and you cannot check their size. This limitation is intentional: it ensures the garbage collector can do its job without interference.
const weakMap = new WeakMap();
const obj = { name: "temporary" };
weakMap.set(obj, "some value");
// When obj has no other references, the entry
// can be garbage collected automatically
obj = null;
// The weakMap entry may disappear at any time
WeakMap in Practice
WeakMap is ideal for associating data with objects without preventing those objects from being collected. This is useful for:
- Private data storage
- Caching with object keys
- DOM node metadata
Private data pattern
WeakMap provides a clean way to attach private data to objects:
const privateData = new WeakMap();
class User {
constructor(name, email) {
// Store private data in the WeakMap
privateData.set(this, { name, email });
}
getName() {
return privateData.get(this).name;
}
getEmail() {
return privateData.get(this).email;
}
}
const user = new User("Alice", "alice@example.com");
console.log(user.getName()); // "Alice"
console.log(user.getEmail()); // "alice@example.com"
The privateData WeakMap doesn’t prevent the User object from being garbage collected. When user goes out of scope, both the object and its private data disappear together.
DOM node metadata
WeakMap shines for attaching metadata to DOM nodes. When elements are removed from the document, their associated tracking data disappears without manual cleanup — you don’t need to remember to call delete() in a teardown function. This pattern is common in frameworks and analytics libraries that instrument the page.
const elementData = new WeakMap();
function trackElement(element) {
const data = {
clicks: 0,
views: 0,
lastInteraction: Date.now()
};
elementData.set(element, data);
element.addEventListener('click', () => {
data.clicks++;
data.lastInteraction = Date.now();
});
return data;
}
// Each element tracks its own metadata
const button = document.querySelector('#myButton');
const stats = trackElement(button);
// When the button is removed from DOM and no longer
// referenced, the WeakMap entry cleans itself up
Caching with WeakMap
Use WeakMap for caching when you want automatic cleanup. The cache stays populated as long as the objects it indexes are alive, but entries for objects that have been collected vanish on their own. This makes WeakMap-based caches self-maintaining — unlike a Map-based cache that would grow indefinitely without manual eviction logic.
const cache = new WeakMap();
function expensiveOperation(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
// Simulate expensive computation
const result = computeExpensiveValue(obj);
cache.set(obj, result);
return result;
}
function computeExpensiveValue(obj) {
// ... expensive computation
return { processed: true, input: obj.id };
}
When the cached objects are no longer referenced elsewhere, the cache entries vanish automatically.
WeakSet in Practice
WeakSet holds a collection of objects. Like WeakMap, entries are garbage collected when the objects have no other references.
WeakSet is useful when you need to mark objects without preventing their collection:
const markedObjects = new WeakSet();
function markAsProcessed(obj) {
markedObjects.add(obj);
return obj;
}
function isProcessed(obj) {
return markedObjects.has(obj);
}
// Usage
const data = { id: 1, values: [1, 2, 3] };
markAsProcessed(data);
console.log(isProcessed(data)); // true
// When data is no longer referenced, it's removed
// from markedObjects automatically
Tracking enabled objects
A practical pattern is tracking which objects should receive special treatment. Unlike a Set-based approach where you’d need to remember to remove entries when objects are discarded, a WeakSet handles cleanup automatically. This is especially useful in UI code where DOM nodes or component instances come and go frequently.
const enabledNodes = new WeakSet();
function enableNode(node) {
enabledNodes.add(node);
node.classList.add('enabled');
}
function disableNode(node) {
enabledNodes.delete(node);
node.classList.remove('enabled');
}
function toggleNode(node) {
if (enabledNodes.has(node)) {
disableNode(node);
} else {
enableNode(node);
}
}
What you cannot do
WeakMap and WeakSet have intentional limitations that prevent iteration, size checks, and enumeration. These restrictions exist to guarantee that the garbage collector can operate without the collection holding objects alive through side effects. If you try any of the operations below, you’ll either get undefined or a TypeError.
const weakMap = new WeakMap();
// These operations DO NOT work:
weakMap.size // undefined
weakMap.keys() // TypeError
weakMap.values() // TypeError
weakMap.entries() // TypeError
weakMap.forEach() // TypeError
for (const key of weakMap) { } // TypeError
The same applies to WeakSet: no iteration, no size, no keys/values/entries.
This is by design. If you could iterate, you’d hold strong references to keys, defeating the purpose.
When to use each
Choose the right collection for your use case:
| Scenario | Use Map | Use WeakMap |
|---|---|---|
| Keys are primitives | Yes | No, only objects |
| Need to iterate keys | Yes | No |
| Need size | Yes | No |
| Keys should be auto-cleaned | No | Yes |
| Memory-sensitive caching | No | Yes |
For WeakSet vs Set, the logic is similar: use WeakSet when you want automatic cleanup and don’t need iteration.
Garbage collection timing
The JavaScript engine decides when to collect entries from WeakMap and WeakSet. You cannot predict or control exactly when cleanup happens:
const weakMap = new WeakMap();
const obj = { data: "important" };
weakMap.set(obj, "value");
// Force a garbage collection (if supported)
if (globalThis.gc) {
globalThis.gc();
}
// obj may or may not be collected at this point
// depending on the engine's implementation
In Node.js, you can force garbage collection with --expose-gc flag and calling globalThis.gc().
Common Misconceptions
Misconception: WeakMap values are weakly held. Only keys are weakly held. Values are regular references. If you hold a strong reference to a value elsewhere, it won’t be collected even if the key is collected.
Misconception: WeakMap is for security. WeakMap provides privacy through obscurity, not true encapsulation. Determined attackers can access WeakMap contents through debugging tools.
Misconception: WeakMap replaces Map entirely. They solve different problems. Use Map when you need iteration or size. Use WeakMap for memory-sensitive object associations.
Modeling ephemeral links
Weak collections are a good fit when one object should remember another object, but only for as long as both are naturally alive. That makes them useful for metadata, caches, and bookkeeping tables that should not keep the main objects alive on their own. If the primary object goes away, the association should be allowed to fade with it.
This style of design keeps the ownership model honest. The main object still owns its own life cycle, while the weak side table only keeps notes. That is a cleaner mental model than storing everything in a long-lived global map, especially in UI code where elements appear and disappear often.
Choosing the right collection
Map and Set are better when you need visibility into the collection itself. If iteration, size, or a stable snapshot matters, use the normal versions. WeakMap and WeakSet are better when cleanup matters more than inspection. That tradeoff is the heart of the design choice.
In practice, the question is usually simple: do you need the collection to be part of the program’s data, or only part of its housekeeping? If it is housekeeping, weak collections are often the cleaner choice. If it is business data, a regular collection is usually easier to debug and reason about.
Pitfalls and Expectations
Weak collections can surprise people when they expect to inspect them later. That is not a bug. It is the cost of letting the engine clean up entries on its own. If you need to see what is inside at any time, pick a different structure. The inability to inspect a weak collection is a reminder that it is not meant to act like a regular list of records.
The safest way to use them is to keep the relationship narrow and obvious. A WeakMap entry should usually answer one question about one object. A WeakSet entry should usually mean “this object has been marked” and nothing more. When the rule stays that small, the collection is much easier to use correctly.
Keeping the rule simple
WeakMap and WeakSet are easiest to use when the rule for membership is simple enough to remember without looking it up. A private metadata slot, a processed flag, or a per-object cache entry are all easy to explain. Once the meaning of the collection starts to branch, the model becomes harder to keep straight.
That simplicity is what makes the collections feel natural in real code. You do not need a complex policy to get value from them. You just need a short, clear relationship that should disappear when the object disappears. When the rule stays that small, the weak collection does its job quietly and well.
Keep ownership clear
Weak collections work best when the object being tracked still feels like the real owner of the data. The weak side table should only remember details that support the object, not become the main place where your app stores state. That makes the code easier to reason about because the lifecycle stays attached to the object that actually matters.
This also keeps cleanup surprises to a minimum. If the only strong reference disappears, the entry should be free to go away without extra book-keeping from you. That is a good sign that the collection is doing exactly the kind of light coordination it was built for.
Practical memory notes
Weak collections are often most useful when they are invisible in day-to-day use. You add an entry, trust the engine to clean it up, and move on. That is a good sign that the design matches the tool. If you find yourself wanting to inspect the collection often, it is probably doing more than it should.
That hidden behavior is the point. The value is in the relationship disappearing on its own, not in the collection becoming a place to store business data.
See Also
- Object Methods — Related static methods on the Object constructor
- Map and Set — Standard Map and Set collections for comparison
- JavaScript Memory Management — Deep dive into how JavaScript handles memory