JavaScript Map, Set, WeakMap, and WeakSet: When to Use Which
Most developers default to objects and arrays. These four data structures solve specific problems better.
Quick Decision Guide
Need key-value pairs?
β Keys are strings/symbols? β Object {}
β Keys are ANY type? β Map πΊοΈ
Need unique values?
β Need to iterate? β Set π¦
β Just membership? β Set π¦
Need key-value with auto-cleanup?
β Keys are objects? β WeakMap π
Need unique objects with auto-cleanup?
β WeakSet π
Map β Key-Value with Any Key Type
// β Object limitations
const obj = {};
obj[{}] = 'value'; // Key becomes "[object Object]"!
obj[{}] = 'other'; // Overwrites! Both {} stringify to same key
obj[{ id: 1 }] = 'first';
obj[{ id: 1 }] = 'second'; // Same problem!
// β
Map accepts any key type
const map = new Map();
const key1 = { id: 1 };
const key2 = { id: 2 };
map.set(key1, 'Alice');
map.set(key2, 'Bob');
map.set(42, 'The Answer');
map.set(true, 'yes');
map.set(undefined, 'no value');
map.get(key1) // 'Alice'
map.get(42) // 'The Answer'
map.has(true) // true
map.size // 5
// Map preserves insertion order (unlike Object!)
const orderedMap = new Map();
orderedMap.set('b', 2);
orderedMap.set('a', 1);
orderedMap.set('c', 3);
Array.from(orderedMap.keys()) // ['b', 'a', 'c']
// Iteration
for (const [key, value] of map) {
console.log(key, 'β', value);
}
map.forEach((value, key) => {
console.log(key, 'β', value);
}
// Convert to/from
const obj = Object.fromEntries(map);
const map2 = new Map(Object.entries(obj));
// Chainable
map.set('x', 1).set('y', 2).set('z', 3);
Set β Unique Values
// Remove duplicates from array
const nums = [1, 2, 3, 2, 1, 4, 5, 3];
const unique = [...new Set(nums)]; // [1, 2, 3, 4, 5]
// Or: Array.from(new Set(nums))
// Set operations
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);
// Union (all unique)
const union = new Set([...a, ...b]); // {1, 2, 3, 4, 5, 6}
// Intersection (common)
const intersection = new Set([...a].filter(x => b.has(x))); // {3, 4}
// Difference (in A not in B)
const difference = new Set([...a].filter(x => !b.has(x))); // {1, 2}
// Membership checking (O(1) vs Array's O(n))
const bigSet = new Set(largeArray);
bigSet.has('needle'); // O(1) instant!
largeArray.includes('needle'); // O(n) slow for big arrays
// Track seen items (deduplication)
function getUniqueUsers(users) {
const seen = new Set();
return users.filter(user => {
if (seen.has(user.email)) return false;
seen.add(user.email);
return true;
});
}
// Set iteration
for (const item of mySet) {
console.log(item);
}
mySet.forEach((value) => {
console.log(value);
});
WeakMap β Key-Value with Garbage Collection
// Problem with Map + object keys:
let user = { name: 'Alice' };
const cache = new Map();
cache.set(user, { posts: 10, comments: 50 });
user = null; // We're done with user
// BUT cache still holds a reference β user object is NOT garbage collected!
// Memory leak! β
// β
WeakMap solves this:
let user2 = { name: 'Bob' };
const weakCache = new WeakMap();
weakCache.set(user2, { posts: 5, comments: 30 });
user2 = null; // Now the user object CAN be garbage collected
// WeakMap entry is automatically removed! β
// WeakMap limitations (by design):
// - No .size property
// - No .keys(), .values(), .entries()
// - Not iterable
// - Keys must be objects (not primitives)
// WHY? Because iteration would prevent garbage collection!
// Practical use cases:
// 1. Private data
const privateData = new WeakMap();
class User {
constructor(name, password) {
this.name = name;
privateData.set(this, { password }); // Password not on the instance!
}
checkPassword(guess) {
return privateData.get(this).password === guess;
}
}
const u = new User('Alice', 'secret');
u.name // 'Alice' (public)
u.password // undefined (private!)
privateData.get(u).password // 'secret' (only accessible via WeakMap)
// 2. Metadata for DOM elements
const elementData = new WeakMap();
function setupElement(el) {
elementData.set(el, {
clickCount: 0,
lastClick: null,
});
el.addEventListener('click', () => {
const data = elementData.get(el);
data.clickCount++;
data.lastClick = Date.now();
});
}
// When element is removed from DOM β data is auto-cleaned!
// No manual cleanup needed!
// 3. Caching expensive computations
const computeCache = new WeakMap();
function expensiveCompute(obj) {
if (computeCache.has(obj)) {
return computeCache.get(obj);
}
const result = /* ... expensive calculation ... */;
computeCache.set(obj, result);
return result;
}
// Cache entries auto-cleanup when objects are no longer referenced
WeakSet β Unique Objects with GC
// Like Set but only holds objects + auto-cleans
// Practical use case: Track "processed" items
const processed = new WeakSet();
function processItem(item) {
if (processed.has(item)) {
return; // Already processed
}
// ... process item ...
processed.add(item);
}
// Practical use case: Mark objects as "tagged"
const tagged = new WeakSet();
function tag(obj) {
tagged.add(obj);
}
function isTagged(obj) {
return tagged.has(obj);
}
// Practical use case: Valid objects
const validElements = new WeakSet();
function validate(el) {
if (!validElements.has(el)) {
throw new Error('Invalid element');
}
}
// When elements are removed from DOM β WeakSet entry auto-cleans
Performance Comparison
// Add 1M entries, then lookup 1000 times:
// Object: ~120ms add, ~0.5ms lookup
// Map: ~150ms add, ~0.3ms lookup
// Set: ~130ms add, ~0.3ms lookup
// Key differences:
// - Object keys are always strings/symbols (auto-coercion)
// - Map keys can be any type (no coercion)
// - Map preserves insertion order, Object does (mostly) since ES2015
// - Map has .size, Object requires Object.keys().length
// - Map is slightly faster for frequent add/delete
// - Object is slightly faster for simple key-value with string keys
When to Use What
| Use Case | Data Structure | Why |
|---|---|---|
| Simple config | Object {} | Familiar, JSON-serializable |
| String key-value | Object {} | Slightly faster for string keys |
| Any-type keys | Map | No key coercion |
| Ordered pairs | Map | Guaranteed insertion order |
| Remove duplicates | Set | [...new Set(arr)] |
| Membership test | Set | O(1) lookup |
| Set operations | Set | Union, intersection, diff |
| Private class data | WeakMap | Auto-cleanup, no leak |
| DOM element metadata | WeakMap | Auto-cleanup |
| Object caching | WeakMap | Auto-cleanup |
| Tag/mark objects | WeakSet | Lightweight, auto-cleanup |
Which data structure do you use most? Any creative use cases?
Follow @armorbreak for more JavaScript content.
Top comments (0)