DEV Community

Alex Chen
Alex Chen

Posted on

JavaScript Map, Set, WeakMap, and WeakSet: When to Use Which

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 πŸ”„
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)