Map, Set, WeakMap, and WeakSet
ES6 introduced Map and Set as first-class collection types, plus WeakMap and WeakSet for memory-sensitive associations. Each solves problems that plain objects and arrays handle awkwardly.
Set — Unique Values
A Set stores unique values of any type. Duplicates are silently ignored.
const tags = new Set(['js', 'web', 'js']);
console.log(tags.size); // 2
tags.add('node');
tags.add('js'); // duplicate — ignored
tags.has('web'); // true
tags.delete('web');
tags.clear(); // remove all
// Iterate
for (const tag of tags) {
console.log(tag);
}
// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1, 2, 3, 4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2, 3}
Remove Duplicates from Arrays
const numbers = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(numbers)]; // [1, 2, 3]
// Unique objects by property (Set can't dedupe objects by field)
const users = [{ id: 1 }, { id: 2 }, { id: 1 }];
const uniqueUsers = [...new Map(users.map(u => [u.id, u])).values()];
Map — Key-Value Pairs with Any Key Type
A Map stores key-value pairs where keys can be any type — objects, functions, symbols, or primitives.
const userRoles = new Map();
userRoles.set('alice', 'admin');
userRoles.set('bob', 'user');
console.log(userRoles.get('alice')); // 'admin'
console.log(userRoles.size); // 2
// Object as key
const sessionKey = { userId: 42 };
userRoles.set(sessionKey, { token: 'abc123' });
console.log(userRoles.get(sessionKey)); // { token: 'abc123' }
// Iterate
for (const [key, value] of userRoles) {
console.log(key, value);
}
userRoles.forEach((value, key) => {
console.log(key, '→', value);
});
Map vs Object
| Feature | Map | Object |
|---|---|---|
| Key types | Any | String or Symbol |
| Size | .size property |
Manual Object.keys().length |
| Iteration order | Guaranteed insertion order | Mostly insertion order (integer keys reorder) |
| Prototype keys | None by default | Inherits from Object.prototype |
| JSON serialization | Not directly serializable | Native JSON.stringify support |
| Performance | Better for frequent add/delete | Better for static record-like data |
Map from Object
const obj = { a: 1, b: 2 };
const map = new Map(Object.entries(obj));
const backToObj = Object.fromEntries(map);
WeakMap — Object Keys with Garbage Collection
WeakMap keys must be objects. If the key object is no longer referenced elsewhere, the entry is automatically garbage-collected.
const privateData = new WeakMap();
class User {
constructor(name) {
privateData.set(this, { name, createdAt: Date.now() });
}
getName() {
return privateData.get(this).name;
}
}
const user = new User('Alice');
console.log(user.getName()); // 'Alice'
// When user is dereferenced, privateData entry is collected
WeakMap has no .size, no iteration, and no way to list keys — by design.
WeakSet — Weak Object References
WeakSet stores objects only. Useful for tracking visited nodes without preventing garbage collection:
const visited = new WeakSet();
function traverse(node) {
if (visited.has(node)) return;
visited.add(node);
// process node...
node.children?.forEach(traverse);
}
Practical Examples
Count Word Frequencies with Map
function countWords(text) {
const counts = new Map();
for (const word of text.toLowerCase().split(/\W+/)) {
if (!word) continue;
counts.set(word, (counts.get(word) || 0) + 1);
}
return counts;
}
countWords("hello world hello"); // Map { 'hello' => 2, 'world' => 1 }
Cache Expensive Computations
const cache = new Map();
function fibonacci(n) {
if (cache.has(n)) return cache.get(n);
if (n <= 1) return n;
const result = fibonacci(n - 1) + fibonacci(n - 2);
cache.set(n, result);
return result;
}
For object-keyed caches where memory matters, use WeakMap instead.
Track Event Listeners for Cleanup
const listeners = new WeakMap();
function addManagedListener(element, event, handler) {
element.addEventListener(event, handler);
if (!listeners.has(element)) {
listeners.set(element, []);
}
listeners.get(element).push({ event, handler });
}
Best Practices
- Use Set when you need uniqueness testing with O(1) lookup.
- Use Map when keys are non-strings or you need guaranteed iteration order.
- Use WeakMap/WeakSet for metadata attached to objects without memory leaks.
- Prefer plain objects for JSON-serializable record structures with string keys.
- Convert Map to array for serialization:
[...map.entries()].
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Set doesn’t dedupe objects | Compared by reference, not value | Use Map keyed by a unique property |
map.get(objKey) returns undefined |
Different object reference as key | Store and reuse the same reference |
| Map not in JSON.stringify | Maps aren’t JSON-native | Convert with Object.fromEntries(map) |
| WeakMap key error | Primitive key used | Keys must be objects |
| Unexpected iteration order on Object | Integer-like keys sort first | Use Map for insertion-order guarantees |
Set for unique values, Map for flexible key-value lookups, WeakMap/WeakSet for object-associated data that should not prevent garbage collection.