JavaScript - Map & Set (WeakMap/WeakSet)

Overview

Estimated time: 35–45 minutes

Learn when and how to use Map and Set instead of plain objects or arrays, and how WeakMap/WeakSet enable memory-safe associations with objects.

Learning Objectives

  • Choose between Object vs Map, and Array vs Set.
  • Use set/get/has/delete, iterate with for..of, and convert between structures.
  • Apply WeakMap/WeakSet for caches and metadata without memory leaks.

Prerequisites

Map vs Object

  • Map keeps insertion order, accepts keys of any type (objects, functions, primitives), has a size property, and offers easy iteration.
  • Object has a prototype chain by default, string/symbol keys only, and requires extra work for size and safe iteration.
// Creating and using a Map
const user = { id: 1 };
const m = new Map();
m.set('name', 'Ada');
m.set(user, { role: 'admin' });

m.get('name');       // 'Ada'
m.has(user);         // true
m.size;              // 2
m.delete('name');    // true

// Iterate (insertion order preserved)
for (const [k, v] of m) {
  console.log(k, v);
}

// Build from pairs and convert back
const fromPairs = new Map([[1, 'one'], [2, 'two']]);
const pairs = Array.from(fromPairs); // [[1,'one'], [2,'two']]

// Object <-> Map bridges
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj)); // [['a',1],['b',2]]
const objFromMap = Object.fromEntries(mapFromObj); // { a:1, b:2 }

Key equality: Map uses SameValueZero, so NaN equals NaN and -0 equals +0.

Cloning and merging maps

const a = new Map([['x', 1]]);
const b = new Map([['y', 2]]);

// Clone
const clone = new Map(a);

// Merge (later entries override earlier ones)
const merged = new Map([...a, ...b, ['x', 99]]); // 'x' becomes 99

Set vs Array

  • Set stores unique values, preserves insertion order, and offers fast membership checks with has.
  • Array allows duplicates and offers rich positional operations but slower membership checks for large data.
// Creating and using a Set
const s = new Set([1,2,2,3]); // {1,2,3}
s.add(4);          // {1,2,3,4}
s.has(3);          // true
s.delete(2);       // {1,3,4}
s.size;            // 3

// Iterate
for (const v of s) {
  console.log(v);
}

// Convert between Set and Array
const unique = [...new Set([3,1,2,3,2,1])]; // [3,1,2]
const setFromArr = new Set([...'hello']);    // {'h','e','l','l','o'}

Set operations

const A = new Set([1,2,3]);
const B = new Set([3,4]);

// Union
const union = new Set([...A, ...B]); // {1,2,3,4}

// Intersection
const intersection = new Set([...A].filter(x => B.has(x))); // {3}

// Difference
const difference = new Set([...A].filter(x => !B.has(x))); // {1,2}

WeakMap and WeakSet

  • WeakMap: keys must be objects; values can be anything. Not iterable. Keys are held weakly: when the object is no longer referenced elsewhere, the entry can be garbage-collected.
  • WeakSet: stores objects only, held weakly; not iterable.
// WeakMap for per-object metadata (avoids leaks)
const meta = new WeakMap();
function tag(obj, info) { meta.set(obj, info); }
function getTag(obj) { return meta.get(obj); }

let node = { kind: 'widget' };
tag(node, { cached: true });
getTag(node); // { cached: true }

// If 'node' goes out of scope and nothing else references it,
// the WeakMap entry can be collected.
node = null; // Later, GC may reclaim both the object and its metadata.

// WeakSet example
const seen = new WeakSet();
function visit(obj) {
  if (seen.has(obj)) return; // already processed
  seen.add(obj);
  // ...process obj...
}

When to use which?

  • Use Map for dynamic key-value stores, especially with non-string keys or frequent inserts/deletes.
  • Use Object for simple, fixed-shape records and when integrating with JSON directly.
  • Use Set to enforce uniqueness and for fast membership tests.
  • Use WeakMap/WeakSet for associating data with objects without preventing garbage collection.

Common Pitfalls

  • Iteration: WeakMap/WeakSet are not iterable. You cannot spread or loop them; only set/get/has/delete (and add for WeakSet).
  • Serialization: JSON.stringify ignores Map/Set. Convert with Array.from(map) or [...set], or Object.fromEntries(map).
  • Key types: WeakMap keys must be objects; primitives throw TypeError.
  • Cloning: new Map(oldMap) performs a shallow copy. Nested objects are shared.

Try it

Run to build a frequency map and unique list: