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

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

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

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

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

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

Which one do you use most? Any creative use cases?

Follow @armorbreak for more JavaScript content.

Top comments (0)