What are the differences between Map/Set and WeakMap/WeakSet?
The short answer
Map and Set are collection types that hold strong references to their contents — as long as something is in a Map or Set, it won't be garbage collected. WeakMap and WeakSet hold weak references instead, meaning they don't prevent garbage collection. If nothing else in your program references an object stored in a WeakMap or WeakSet, that object can be cleaned up, and its entry silently disappears. This makes the Weak variants ideal for associating metadata with objects without creating memory leaks.
Why Map and Set were added
Before ES6, if you wanted a key-value store, you used a plain object. But objects have limitations as collections:
- Keys can only be strings or symbols. If you use an object as a key, it gets coerced to
"[object Object]". - You inherit properties from
Object.prototype, so keys like"constructor"or"toString"can collide. - There's no built-in way to get the size, and iteration order wasn't guaranteed.
Map fixes all of this:
const map = new Map();
const objKey = { id: 1 };
const fnKey = () => {};
map.set(objKey, 'associated with an object');
map.set(fnKey, 'associated with a function');
map.set(42, 'associated with a number');
map.get(objKey); // "associated with an object"
map.size; // 3Keys can be anything — objects, functions, numbers, even NaN. Iteration order is guaranteed (insertion order), and map.size tells you how many entries there are.
Set is the same idea but for unique values — no duplicates allowed:
const set = new Set([1, 2, 3, 2, 1]);
set.size; // 3 — duplicates removed
set.has(2); // true
set.add(4);
set.delete(1);Sets are perfect for deduplication, tracking unique items, and fast membership checks.
The memory problem
Here's where things get subtle. Both Map and Set hold strong references to their contents. If you store an object in a Map, that object cannot be garbage collected — even if nothing else in your program references it anymore.
const cache = new Map();
function processUser(user) {
if (cache.has(user)) {
return cache.get(user);
}
const result = expensiveComputation(user);
cache.set(user, result);
return result;
}This looks reasonable, but there's a problem. Every user object that passes through processUser lives forever in the cache Map, even after the rest of your application has moved on from that user. Over time, this cache grows without bound. It's a memory leak.
You could manually clean up with cache.delete(), but tracking when an object is "no longer needed" is exactly the kind of thing garbage collection is supposed to handle for you.
Enter WeakMap and WeakSet
A WeakMap is like a Map, but with a crucial difference: it holds weak references to its keys. If nothing else in your program references a key, the garbage collector can reclaim both the key and its associated value.
const metadata = new WeakMap();
function trackElement(element) {
metadata.set(element, {
clickCount: 0,
lastInteraction: Date.now(),
});
}If the DOM element is removed and no other code holds a reference to it, the garbage collector can clean it up, and the WeakMap entry vanishes automatically. No manual cleanup needed, no memory leak.
WeakSet works the same way — it holds weak references to its values:
const visited = new WeakSet();
function markVisited(node) {
visited.add(node);
}
function hasVisited(node) {
return visited.has(node);
}If a node is garbage collected, it's silently removed from the WeakSet.
The constraints of Weak collections
Weak references come with trade-offs. Because the garbage collector can remove entries at any time, certain operations are impossible:
Keys must be objects (or non-registered symbols). Primitive values like strings and numbers can't be weakly referenced because they aren't garbage collected the same way — identical primitives share the same identity. So weakMap.set('hello', value) throws a TypeError.
No iteration. You can't loop over a WeakMap or WeakSet. There's no forEach, no keys(), no values(), no entries(), and no for...of. The garbage collector might remove entries between iterations, so the set of entries is non-deterministic at any given moment.
No size property. For the same reason, you can't ask how many entries a WeakMap or WeakSet contains.
WeakMap methods are limited to: get, set, has, delete.
WeakSet methods are limited to: add, has, delete.
You can look things up by key, but you can't enumerate what's inside. Think of them as a one-way association — you can ask "is this object tracked?" but not "what objects are tracked?"
Practical use cases
Caching with WeakMap. Associate computed results with objects without preventing those objects from being garbage collected:
const cache = new WeakMap();
function getComputedStyle(element) {
if (cache.has(element)) {
return cache.get(element);
}
const styles = computeExpensiveStyles(element);
cache.set(element, styles);
return styles;
}When the element is removed from the DOM and nothing else references it, both the element and its cached styles are cleaned up.
Private data. Before private class fields (#field) existed, WeakMap was the standard pattern for truly private instance data:
const privateData = new WeakMap();
class User {
constructor(name, secret) {
privateData.set(this, { secret });
this.name = name;
}
getSecret() {
return privateData.get(this).secret;
}
}The secret isn't accessible from outside the module. And when a User instance is garbage collected, its private data goes with it.
Tracking DOM elements. Keep metadata about DOM nodes without leaking memory when those nodes are removed:
const observers = new WeakMap();
function observe(element, callback) {
const observer = new MutationObserver(callback);
observer.observe(element, { childList: true });
observers.set(element, observer);
}
function unobserve(element) {
const observer = observers.get(element);
if (observer) {
observer.disconnect();
observers.delete(element);
}
}Deduplication with Set. When you need to track unique primitive or object values and you do need to enumerate them:
function uniqueBy(array, keyFn) {
const seen = new Set();
return array.filter((item) => {
const key = keyFn(item);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}Marking with WeakSet. Track whether you've already processed an object without preventing it from being collected:
const processed = new WeakSet();
function processOnce(obj) {
if (processed.has(obj)) return;
processed.add(obj);
doExpensiveWork(obj);
}Quick comparison
| Feature | Map / Set | WeakMap / WeakSet |
|---|---|---|
| Key types | Any value | Objects only |
| References | Strong | Weak |
| Iterable | Yes | No |
.size | Yes | No |
| Garbage collection | Prevents it | Allows it |
| Use case | General-purpose collections | Associating data with objects without leaking memory |
Why interviewers ask this
This question tests whether you understand JavaScript's memory model. Knowing what Map and Set do is table stakes — the interesting part is explaining why the Weak variants exist and when you'd choose them. It touches on garbage collection, memory leaks (a real production concern), and the trade-offs that come with weak references. Candidates who can articulate a concrete use case like DOM element tracking or object-keyed caching demonstrate they've thought about memory management in practice, not just in theory.