JavaScript Map, Set, WeakMap, and WeakSet: When to Use Which
Objects and arrays aren't your only options. These 4 data structures solve specific problems better.
Map — Better Object for Keys
// ❌ Object limitations:
// - Keys are always strings (or symbols)
// - No size property
// - Not directly iterable
const obj = {};
obj[{}] = 'value'; // Key becomes "[object Object]"!
obj[{}] = 'other'; // Overwrites previous!
// ✅ Map: Any type as key, built-in size, iterable
const cache = new Map();
cache.set('key', 'value');
cache.set(123, 'number key');
cache.set({ id: 1 }, 'object key'); // Each object is unique!
cache.set(() => {}, 'function key');
cache.get('key'); // 'value'
cache.has('key'); // true
cache.size; // 4
cache.delete('key'); // true
cache.clear(); // Remove all
// Iterate
for (const [key, value] of cache) {
console.log(key, value);
}
// Create from array of entries
const userMap = new Map([
[1, { name: 'Alex' }],
[2, { name: 'Sam' }],
[3, { name: 'Charlie' }],
]);
Set — Unique Values Only
// Remove duplicates from array
const numbers = [1, 2, 3, 2, 1, 4, 3, 5];
const unique = [...new Set(numbers)]; // [1, 2, 3, 4, 5]
const uniqueWords = [...new Set('hello world hello'.split(' '))];
// ['hello', 'world']
// Set operations
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Union
const union = new Set([...setA, ...setB]); // {1,2,3,4,5,6}
// Intersection
const intersection = new Set([...setA].filter(x => setB.has(x))); // {3,4}
// Difference (A - B)
const difference = new Set([...setA].filter(x => !setB.has(x))); // {1,2}
// Useful for tracking state
const visitedUrls = new Set();
const activeUsers = new Set();
const selectedItems = new Set();
selectedItems.add('item1');
selectedItems.has('item1'); // true
selectedItems.delete('item1'); // true
selectedItems.size; // 0
WeakMap — Memory-Safe Object-Keyed Map
// Key difference from Map: Keys MUST be objects, values are garbage-collected
// when the key object has no other references
// Use case 1: Private data
const privateData = new WeakMap();
class User {
constructor(name, password) {
this.name = name;
privateData.set(this, { password }); // Can't access from outside!
}
checkPassword(pw) {
return privateData.get(this).password === pw;
}
}
// Use case 2: Caching expensive computations
const expensiveCache = new WeakMap();
function processData(obj) {
if (expensiveCache.has(obj)) {
return expensiveCache.get(obj); // Cached result
}
const result = heavyComputation(obj);
expensiveCache.set(obj, result);
// When obj is garbage collected → cache entry is auto-removed!
return result;
}
// Use case 3: Tracking DOM elements
const elementData = new WeakMap();
function setupElement(el) {
elementData.set(el, { clickCount: 0 });
el.addEventListener('click', () => {
const data = elementData.get(el);
data.clickCount++;
console.log(`Clicked ${data.clickCount} times`);
});
}
// When element is removed from DOM → data is auto-cleaned!
WeakSet — Memory-Safe Object Set
// Keys must be objects, no iteration, auto-garbage-collected
// Use case 1: Mark objects as "processed"
const processed = new WeakSet();
function processItems(items) {
for (const item of items) {
if (processed.has(item)) continue; // Skip already processed
// ... process item ...
processed.add(item);
}
}
// Use case 2: Track "active" objects
const activeElements = new WeakSet();
function onScroll(el) {
if (activeElements.has(el)) return;
activeElements.add(el);
el.classList.add('visible');
}
// Use case 3: Branding / type checking
const brand = new WeakSet();
function createTagged() {
const obj = { /* ... */ };
brand.add(obj);
return obj;
}
function isTagged(obj) {
return brand.has(obj);
}
Comparison Table
| Feature | Object | Map | Set | WeakMap | WeakSet |
|---|---|---|---|---|---|
| Key types | string/symbol | Any | — | Object only | Object only |
| Value types | Any | Any | — | Any | — |
| Size | Object.keys().length | .size | .size | No | No |
| Iterable | Object.entries() | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
| GC safe | No | No | No | ✅ Yes | ✅ Yes |
| Best for | Records | Caching | Unique values | Private data | Tracking |
When to Use What
Need key-value pairs?
├── Keys are strings → Object (or Map, both work)
├── Keys can be any type → Map
├── Keys are objects, auto-cleanup needed → WeakMap
└── Need size/iteration → Map
Need unique values only?
├── Need iteration → Set
└── Object tracking, auto-cleanup → WeakSet
Examples:
- API response cache → Map (any key type + size)
- User session store → Map (fast lookup by session ID)
- Unique tags → Set (auto-dedup)
- Private class fields → WeakMap (auto-cleanup)
- DOM element tracking → WeakMap/WeakSet (auto-cleanup)
- Counting occurrences → Map (key → count)
- BFS/DFS visited set → Set (fast .has())
Which one do you use most? Any creative use cases?
Follow @armorbreak for more JavaScript content.
Top comments (0)