|$ curl https://forge-ai.dev/api/markdown?path=docs/js/set-map
$cat docs/javascript-set-&-map.md
updated Last week·25 min read·published

JavaScript Set & Map

JavaScriptData StructuresCollectionsIntermediate🎯Free Tools
Introduction

JavaScript provides two powerful collection types beyond plain objects and arrays: Map and Set. Both were introduced in ES6 and solve specific problems that plain objects and arrays handle poorly. Map is a key-value store that accepts any type as a key, while Set stores unique values of any type.

Unlike plain objects, Maps preserve insertion order, have a size property, and support efficient iteration. Unlike arrays, Sets enforce uniqueness automatically and provide O(1) membership testing. Together, these collections form the foundation for caches, deduplication, metadata storage, and many other patterns.

This guide covers Map, Set, WeakMap, and WeakSet in depth, with practical examples and guidance on when to use each type.

set-map-intro.js
JavaScript
1// Quick overview
2const map = new Map(); // Key-value pairs (any key type)
3const set = new Set(); // Unique values
4const weakMap = new WeakMap(); // GC-friendly key-value (object keys only)
5const weakSet = new WeakSet(); // GC-friendly membership (object keys only)
6
7// Map vs Object
8const obj = { a: 1, b: 2 }; // String keys only
9const mp = new Map([["a", 1], ["b", 2]]); // Any keys
10
11// Set vs Array
12const arr = [1, 2, 2, 3]; // Duplicates allowed
13const st = new Set([1, 2, 2, 3]); // Duplicates removed
Map — Key-Value Pairs

A Map holds key-value pairs where keys can be any JavaScript type — objects, functions, primitives, even NaN. Maps remember insertion order, provide O(1) lookups, and expose a size property for instant length checks.

Map Methods

MethodDescription
set(key, value)Add or update an entry (chainable)
get(key)Retrieve value, undefined if missing
has(key)Check if key exists
delete(key)Remove entry (returns boolean)
clear()Remove all entries
sizeNumber of entries (property)
forEach(cb)Iterate in insertion order
map-operations.js
JavaScript
1// Basic Map operations
2const userMap = new Map();
3
4// Keys can be any type
5userMap.set("name", "Alice");
6userMap.set(42, "the answer");
7userMap.set(true, "boolean key");
8userMap.set(NaN, "NaN is a valid key");
9
10console.log(userMap.get("name")); // "Alice"
11console.log(userMap.get(42)); // "the answer"
12console.log(userMap.get(NaN)); // "NaN is a valid key"
13console.log(userMap.size); // 4
14console.log(userMap.has("name")); // true
15
16// Chain set() calls
17userMap.set("admin", true)
18 .set("role", "editor")
19 .set("active", true);
20
21// Iteration in insertion order
22for (const [key, value] of userMap) {
23 console.log(key, value);
24}
25
26// Convert to/from arrays
27const entries = [...userMap]; // Array of [key, value] pairs
28const restored = new Map(entries); // Restore from array
29
30// Object to Map and back
31const obj = { a: 1, b: 2, c: 3 };
32const fromObj = new Map(Object.entries(obj));
33const backToObj = Object.fromEntries(fromObj);
map-practical.js
JavaScript
1// Practical: memoization with Map
2function memoize(fn) {
3 const cache = new Map();
4 return function (...args) {
5 const key = JSON.stringify(args);
6 if (cache.has(key)) {
7 return cache.get(key);
8 }
9 const result = fn.apply(this, args);
10 cache.set(key, result);
11 return result;
12 };
13}
14
15const fibonacci = memoize((n) => {
16 if (n <= 1) return n;
17 return fibonacci(n - 1) + fibonacci(n - 2);
18});
19
20console.log(fibonacci(100)); // 354224848179262000000 — fast with cache
21
22// Practical: counting occurrences
23function countWords(text) {
24 const counts = new Map();
25 const words = text.toLowerCase().split(/\s+/);
26
27 for (const word of words) {
28 counts.set(word, (counts.get(word) || 0) + 1);
29 }
30
31 return counts;
32}
33
34const wordCount = countWords("hello world hello javascript");
35console.log([...wordCount.entries()]);
36// [["hello", 2], ["world", 1], ["javascript", 1]]
Map vs Object

Both Map and Object store key-value pairs, but they serve different purposes. Choose Object for fixed string keys known at author time. Choose Map for dynamic keys, especially non-string types, frequent additions/deletions, or when you need reliable size tracking.

FeatureMapObject
Key typesAnyStrings & Symbols only
OrderInsertion orderInteger keys first
sizeO(1) via .sizeO(n) via Object.keys()
IterationDirectly iterableVia Object.keys/values/entries
JSONNo direct supportNative JSON support

best practice

Use Map for dynamic dictionaries with non-string keys, frequent mutations, or when iteration order matters. Use Object for fixed configuration records, JSON-serialized data, or when you need destructuring. For most modern code, Map is the better default.
Set — Unique Values

A Set stores unique values of any type. It uses SameValueZero equality — each value can appear only once. Sets are ideal for deduplication, membership testing, and as building blocks for set operations.

Set Methods

MethodDescription
add(value)Add value (returns the Set for chaining)
has(value)Check if value exists
delete(value)Remove value (returns boolean)
clear()Remove all values
sizeNumber of values
forEach(cb)Iterate in insertion order
set-operations.js
JavaScript
1// Basic Set operations
2const set = new Set();
3
4set.add(1);
5set.add(2);
6set.add(3);
7set.add(1); // Ignored — already exists
8
9console.log(set.size); // 3
10console.log(set.has(2)); // true
11console.log(set.has(4)); // false
12
13set.delete(2);
14console.log(set.has(2)); // false
15
16// Initialize from iterable
17const fromArray = new Set([1, 2, 2, 3, 3, 4]);
18console.log(fromArray.size); // 4 — duplicates removed
19console.log([...fromArray]); // [1, 2, 3, 4]
20
21// Iteration in insertion order
22for (const val of set) {
23 console.log(val);
24}
25
26// Practical: deduplicate arrays
27const duplicates = [1, 2, 2, 3, 4, 4, 5];
28const unique = [...new Set(duplicates)]; // [1, 2, 3, 4, 5]
29
30// Practical: check all values unique
31function allUnique(arr) {
32 return new Set(arr).size === arr.length;
33}
34console.log(allUnique([1, 2, 3])); // true
35console.log(allUnique([1, 2, 2])); // false
set-operations-helpers.js
JavaScript
1// Set operations (union, intersection, difference)
2function union(a, b) {
3 return new Set([...a, ...b]);
4}
5
6function intersection(a, b) {
7 return new Set([...a].filter(x => b.has(x)));
8}
9
10function difference(a, b) {
11 return new Set([...a].filter(x => !b.has(x)));
12}
13
14function symmetricDifference(a, b) {
15 return new Set([
16 ...[...a].filter(x => !b.has(x)),
17 ...[...b].filter(x => !a.has(x))
18 ]);
19}
20
21// Usage
22const A = new Set([1, 2, 3, 4, 5]);
23const B = new Set([4, 5, 6, 7, 8]);
24
25console.log([...union(A, B)]); // [1,2,3,4,5,6,7,8]
26console.log([...intersection(A, B)]); // [4, 5]
27console.log([...difference(A, B)]); // [1, 2, 3]
28console.log([...symmetricDifference(A, B)]); // [1,2,3,6,7,8]
29
30// Practical: permission checking
31const userPermissions = new Set(["read", "write", "delete"]);
32const requiredPermissions = new Set(["read", "write"]);
33
34function hasAllPermissions(user, required) {
35 return [...required].every(p => user.has(p));
36}
37
38console.log(hasAllPermissions(userPermissions, requiredPermissions)); // true

info

set.has(value) is O(1) on average, compared to array.includes(value) which is O(n). For large collections, this performance difference is dramatic — 1000 checks on 10,000 items: 1M operations vs 10 operations.
WeakMap — GC-Friendly Keys

A WeakMap is like a Map but its keys are held weakly. If there are no other references to a key object, both the key and its value are eligible for garbage collection. This makes WeakMaps perfect for private data, metadata attached to objects, and caches without memory leaks.

WeakMaps have constraints: keys must be objects (not primitives), there is no size property or iteration methods, and they cannot be cleared. These constraints exist because contents could change at any time due to garbage collection.

weakmap-basics.js
JavaScript
1// WeakMap basics — object keys only
2const weakMap = new WeakMap();
3
4let user = { id: 1, name: "Alice" };
5weakMap.set(user, "session-token-abc123");
6
7console.log(weakMap.get(user)); // "session-token-abc123"
8console.log(weakMap.has(user)); // true
9
10// When user is dereferenced, the entry can be GC'd
11user = null;
12
13// Private data with WeakMap
14const _private = new WeakMap();
15
16class Person {
17 constructor(name, ssn) {
18 _private.set(this, { ssn });
19 this.name = name;
20 }
21
22 getSSN() {
23 return _private.get(this).ssn;
24 }
25}
26
27const person = new Person("Alice", "123-45-6789");
28console.log(person.name); // "Alice"
29console.log(person.ssn); // undefined — not accessible
30console.log(person.getSSN()); // "123-45-6789" — via privileged method
weakmap-practical.js
JavaScript
1// Practical: DOM node metadata without memory leaks
2const nodeData = new WeakMap();
3
4function trackElement(el) {
5 nodeData.set(el, {
6 created: Date.now(),
7 clickCount: 0,
8 data: {}
9 });
10}
11
12document.querySelectorAll(".tracked").forEach((el) => {
13 trackElement(el);
14 el.addEventListener("click", () => {
15 const data = nodeData.get(el);
16 data.clickCount++;
17 console.log(`Clicked ${data.clickCount} times`);
18 });
19});
20// When element is removed from DOM, WeakMap entry
21// automatically becomes eligible for garbage collection
22
23// Practical: caching on objects
24const cache = new WeakMap();
25
26function computeExpensive(obj) {
27 if (cache.has(obj)) {
28 return cache.get(obj);
29 }
30 const result = expensiveComputation(obj);
31 cache.set(obj, result);
32 return result;
33}
34// When obj is GC'd, cache entry is cleaned up — no leak!
🔥

pro tip

WeakMapis the canonical implementation of the "private data" pattern in JavaScript. Unlike closures, which capture variables for the lifetime of the object, WeakMaps allow truly private properties that can be garbage collected when the instance is no longer needed.
WeakSet — GC-Friendly Membership

WeakSet is the set analog of WeakMap. It stores unique objects with weak references — if the object is otherwise unreferenced, it is removed from the set. WeakSet has no iteration, no size, and only accepts object values. It is useful for tagging or marking objects without preventing garbage collection.

weakset.js
JavaScript
1// WeakSet basics
2const visited = new WeakSet();
3
4let page1 = { url: "/home" };
5let page2 = { url: "/about" };
6
7visited.add(page1);
8visited.add(page2);
9
10console.log(visited.has(page1)); // true
11page1 = null; // page1 removed from visited automatically when GC'd
12
13// Practical: prevent double-processing
14const processed = new WeakSet();
15
16function processItem(item) {
17 if (processed.has(item)) {
18 return; // skip already processed
19 }
20 // ... process item ...
21 processed.add(item);
22}
23
24const items = [{ id: 1 }, { id: 2 }, { id: 1 }];
25items.forEach(processItem); // id:1 processed once, id:2 processed once
26
27// Practical: marking objects for special handling
28const isAuthorized = new WeakSet();
29
30function grantAccess(user) {
31 isAuthorized.add(user);
32}
33
34function checkAccess(user) {
35 return isAuthorized.has(user);
36}

info

Use WeakSet when you need to tag or mark objects without preventing GC. Common use cases: tracking which objects have been visited, processed, serialized, or observed. Unlike Set, you don't need to manually remove entries when objects are done — GC handles it automatically.
When to Use Each

Choosing the right collection type depends on your specific needs. Here is a decision guide to help you pick the right tool for the job.

when-to-use.js
JavaScript
1// Decision guide
2
3// Use Map when:
4// 1. Dynamic keys, especially non-string
5const userRoles = new Map();
6userRoles.set(userObject, "admin");
7
8// 2. Frequent additions/removals
9const cache = new Map();
10cache.set("key", value);
11cache.delete("key");
12
13// 3. Need reliable iteration or size
14console.log(cache.size);
15for (const [k, v] of cache) { /* ... */ }
16
17// Use Object when:
18// 1. Fixed string keys known at author time
19const config = { host: "localhost", port: 3000 };
20
21// 2. JSON serialization needed
22const json = JSON.stringify(config);
23
24// 3. Destructuring pattern
25const { host, port } = config;
26
27// Use Set when:
28// 1. Need unique values
29const unique = [...new Set(array)];
30
31// 2. Fast membership testing
32const lookup = new Set(ids);
33if (lookup.has(target)) { /* O(1) */ }
34
35// Use WeakMap when:
36// 1. Private data attached to objects
37// 2. Caches keyed on objects (auto-cleanup)
38// 3. DOM element metadata (no memory leaks)
39
40// Use WeakSet when:
41// 1. Marking objects (visited, processed)
42// 2. Membership without preventing GC

warning

Never use WeakMap or WeakSet if you need to iterate over entries, check size, or if your keys are primitives. These collections exist specifically for memory-sensitive scenarios where object keys have a limited lifetime. Using them where a regular Map or Set would work adds unnecessary complexity.
Best Practices
Initialize collections with iterables: new Map(entries), new Set(values) — avoids multiple .set()/.add() calls
Use Object.fromEntries() to convert Map to Object when you need JSON serialization
Use Set for uniqueness enforcement — set.has() is O(1) vs array.includes() O(n)
Use WeakMap for private data attached to specific object instances — prevents memory leaks
Use WeakSet for marking/tagging objects without preventing GC — ideal for visited tracking
Convert between Map and Object freely: new Map(Object.entries(obj)) and Object.fromEntries(map)
For set operations (union, intersection, difference), convert to arrays and filter — native methods are coming soon
$Blueprint — Engineering Documentation·Section ID: JS-SETMAP·Revision: 1.0