|$ curl https://forge-ai.dev/api/markdown?path=docs/js/collections
$cat docs/javascript-—-maps,-sets-&-weakrefs.md
updated Recently·35 min read·published

JavaScript — Maps, Sets & WeakRefs

JavaScriptCollectionsAdvancedIntermediate to Advanced
Introduction

Modern JavaScript provides rich collection types beyond plain objects and arrays. Map and Set were introduced in ES6 to address the limitations of using objects as maps and arrays as sets. WeakMap and WeakSet enable memory-efficient associations where keys can be garbage-collected. WeakRef and FinalizationRegistry (ES2021) provide low-level access to the garbage collector's behavior.

These collections are not just convenience APIs — they offer real performance and correctness advantages. Maps preserve insertion order, accept any key type, and have predictable O(1) lookups. Sets enforce uniqueness automatically. Weak collections enable patterns like caching and metadata without preventing garbage collection.

Choosing the right collection type is a key engineering decision. This guide covers each type in depth, with practical patterns, performance benchmarks, and decision criteria for choosing between them.

collections-intro.js
JavaScript
1// Overview of collection types
2const map = new Map(); // Any-key key-value store
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)
6const ref = new WeakRef(target); // Weak reference to an object
7const registry = new FinalizationRegistry(callback); // Cleanup notification
8
9// Side-by-side comparison
10const obj = { key: "value" }; // Traditional object (string-only keys)
11const mp = new Map([["key", "value"]]); // Map (any keys)
12const st = new Set([1, 2, 3]); // Set (unique values)
13
14console.log(obj.key); // "value"
15console.log(mp.get("key")); // "value"
16console.log(st.has(2)); // true
Map — Any-Key Objects with Order

A Map holds key-value pairs where keys can be any JavaScript type — objects, functions, primitives, even NaN. Unlike plain objects, Maps remember insertion order, have a size property, and provide predictable performance for all operations.

Map API

Method/PropertyDescription
set(key, value)Add or update an entry (chainable)
get(key)Retrieve value, returns undefined if missing
has(key)Check if key exists (boolean)
delete(key)Remove entry (returns boolean success)
clear()Remove all entries
sizeNumber of entries (property, not method)
keys()Iterator over keys
values()Iterator over values
entries()Iterator over [key, value] pairs
forEach(cb)Iterate in insertion order
map-basics.js
JavaScript
1// Basic Map operations
2const userMap = new Map();
3
4// Keys can be any type
5const key1 = { id: 1 };
6const key2 = "stringKey";
7const key3 = 42;
8const key4 = NaN; // NaN works as a key (per spec, NaN === NaN in Map)
9
10userMap.set(key1, "Alice");
11userMap.set(key2, "Bob");
12userMap.set(key3, "Charlie");
13userMap.set(key4, "NaN value");
14
15console.log(userMap.get(key1)); // "Alice"
16console.log(userMap.get(key2)); // "Bob"
17console.log(userMap.get(42)); // "Charlie"
18console.log(userMap.get(NaN)); // "NaN value" — NaN is treated equal to itself
19
20console.log(userMap.has(key1)); // true
21console.log(userMap.size); // 4
22
23// Chain set calls
24userMap.set("admin", true).set("role", "editor").set("active", true);
25
26// Iteration (insertion order)
27for (const [key, value] of userMap) {
28 console.log(key, value);
29}
30
31// Convert to/from arrays
32const arr = [...userMap]; // Array of [key, value] pairs
33const restored = new Map(arr); // Restore from array
34
35// Object to Map
36const obj = { a: 1, b: 2, c: 3 };
37const objMap = new Map(Object.entries(obj)); // Map(3) {"a" => 1, "b" => 2, "c" => 3}
38
39// Map to Object
40const fromMap = Object.fromEntries(objMap); // { a: 1, b: 2, c: 3 }
map-reference.js
JavaScript
1// Object keys and reference equality
2const map = new Map();
3
4const alice = { name: "Alice" };
5const bob = { name: "Bob" };
6
7map.set(alice, "admin");
8map.set(bob, "user");
9
10// Map uses SameValueZero equality for keys
11// For objects, this means reference equality
12console.log(map.get({ name: "Alice" })); // undefined — different reference
13console.log(map.get(alice)); // "admin" — same reference
14
15// Practical: DOM element metadata
16const buttonMeta = new Map();
17
18document.querySelectorAll("button").forEach((btn) => {
19 buttonMeta.set(btn, {
20 clickCount: 0,
21 lastClicked: null,
22 handler: () => {
23 const meta = buttonMeta.get(btn);
24 meta.clickCount++;
25 meta.lastClicked = Date.now();
26 }
27 });
28 btn.addEventListener("click", buttonMeta.get(btn).handler);
29});
30
31// Practical: memoization with Map
32function memoize(fn) {
33 const cache = new Map();
34 return function(arg) {
35 if (cache.has(arg)) {
36 console.log("Cache hit:", arg);
37 return cache.get(arg);
38 }
39 console.log("Cache miss:", arg);
40 const result = fn(arg);
41 cache.set(arg, result);
42 return result;
43 };
44}
45
46const fib = memoize((n) => n <= 1 ? n : fib(n - 1) + fib(n - 2));
47console.log(fib(40)); // 102334155 — efficiently computed

best practice

Use Map when: you need non-string keys, you need to iterate frequently, the map will grow/shrink dynamically, or you need reliable size tracking. Use plain objects when: you have fixed string keys known at author time, you need JSON serialization, or you need prototype-based property lookup.
Set — Unique Values

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

set-basics.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 (insertion order)
22for (const val of set) {
23 console.log(val);
24}
25
26set.forEach((val) => console.log(val));
27
28// Practical: deduplicate arrays
29const duplicates = [1, 2, 2, 3, 4, 4, 5];
30const unique = [...new Set(duplicates)]; // [1, 2, 3, 4, 5]
31
32// Practical: deduplicate objects by a property
33const users = [
34 { id: 1, name: "Alice" },
35 { id: 2, name: "Bob" },
36 { id: 1, name: "Alice" }, // duplicate
37];
38const seen = new Set();
39const uniqueUsers = users.filter((user) => {
40 if (seen.has(user.id)) return false;
41 seen.add(user.id);
42 return true;
43});
44
45// Practical: check all values unique
46function allUnique(arr) {
47 return new Set(arr).size === arr.length;
48}
49console.log(allUnique([1, 2, 3])); // true
50console.log(allUnique([1, 2, 2])); // false

info

Use Set for membership testing when order does not matter. 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.
Set Operations

JavaScript's Set does not have built-in union, intersection, or difference methods (yet — they are coming in a future proposal). However, you can implement them efficiently in a few lines. These operations are essential for data processing, permission systems, and state management.

set-operations.js
JavaScript
1// Set operation helpers
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([...a].filter(x => !b.has(x)).concat([...b].filter(x => !a.has(x))));
16}
17
18function isSubset(a, b) {
19 return [...a].every(x => b.has(x));
20}
21
22function isSuperset(a, b) {
23 return [...b].every(x => a.has(x));
24}
25
26// Usage
27const A = new Set([1, 2, 3, 4, 5]);
28const B = new Set([4, 5, 6, 7, 8]);
29
30console.log([...union(A, B)]); // [1, 2, 3, 4, 5, 6, 7, 8]
31console.log([...intersection(A, B)]); // [4, 5]
32console.log([...difference(A, B)]); // [1, 2, 3]
33console.log([...difference(B, A)]); // [6, 7, 8]
34console.log([...symmetricDifference(A, B)]); // [1, 2, 3, 6, 7, 8]
35console.log(isSubset(new Set([1, 2]), A)); // true
36console.log(isSuperset(A, new Set([1, 2]))); // true
37
38// Practical: permission system
39const userPermissions = new Set(["read", "write", "delete"]);
40const requiredPermissions = new Set(["read", "write"]);
41
42function hasAllPermissions(user, required) {
43 return isSuperset(user, required);
44}
45
46console.log(hasAllPermissions(userPermissions, requiredPermissions)); // true
47
48// Practical: tagging system
49function getCommonTags(tagSets) {
50 return tagSets.reduce((acc, tags) => intersection(acc, tags));
51}
52
53const tag1 = new Set(["js", "react", "web"]);
54const tag2 = new Set(["js", "react", "mobile"]);
55const tag3 = new Set(["js", "vue", "web"]);
56console.log([...getCommonTags([tag1, tag2, tag3])]); // ["js"]
🔥

pro tip

Set operations are O(n + m) when implemented with filter and has. For very large sets (10,000+ items), avoid converting to arrays repeatedly — consider implementing manual iteration. The Set proposal for native union(), intersection(), difference(), and isSubsetOf() methods is in stage 4 and will ship soon in most environments.
WeakMap — Garbage-Collectible Keys

A WeakMap is like a Map but with a critical difference: 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 important constraints: keys must be objects (not primitives), there is no size property or iteration methods (keys(), values(), entries()), and they cannot be cleared. These constraints exist because the contents could change at any time due to garbage collection — iteration would be unreliable.

FeatureMapWeakMap
Key typesAnyObjects only
Iteration
size
clear()
Garbage collectionPrevents GCPermits GC
Memory leak riskHigh (retains keys)Low (releases keys)
weakmap-basics.js
JavaScript
1// WeakMap basics — object keys only
2const weakMap = new WeakMap();
3
4let user = { id: 1, name: "Alice" };
5
6weakMap.set(user, "session-token-abc123");
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// At this point, the WeakMap entry may be garbage collected
13// weakMap.get(user) would now return undefined (user is null)
14
15// Private data with WeakMap
16const _private = new WeakMap();
17
18class Person {
19 constructor(name, ssn) {
20 // Store private data in WeakMap — not accessible from outside
21 _private.set(this, { ssn });
22 this.name = name;
23 }
24
25 getSSN() {
26 return _private.get(this).ssn;
27 }
28}
29
30const person = new Person("Alice", "123-45-6789");
31console.log(person.name); // "Alice"
32console.log(person.ssn); // undefined — not accessible
33console.log(person.getSSN()); // "123-45-6789" — accessed via privileged method
34
35// Preventing prototype pollution: use WeakMap instead of private properties
36const metadata = new WeakMap();
37
38function setMetadata(obj, data) {
39 metadata.set(obj, data);
40}
41
42function getMetadata(obj) {
43 return metadata.get(obj);
44}
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
12// When element is removed from DOM, the WeakMap entry
13// automatically becomes eligible for garbage collection
14// No explicit cleanup needed!
15
16document.querySelectorAll(".tracked").forEach((el) => {
17 trackElement(el);
18 el.addEventListener("click", () => {
19 const data = nodeData.get(el);
20 data.clickCount++;
21 console.log(`Clicked ${data.clickCount} times`);
22 });
23});
24
25// Practical: caching computed results on objects
26const cache = new WeakMap();
27
28function computeExpensive(obj) {
29 if (cache.has(obj)) {
30 console.log("Returning cached result");
31 return cache.get(obj);
32 }
33 const result = expensiveComputation(obj);
34 cache.set(obj, result);
35 return result;
36}
37
38// When obj is no longer used elsewhere, the cache entry
39// is automatically cleaned up — no memory leak!
40
41// Practical: event handler cleanup
42const handlerMap = new WeakMap();
43
44function addTrackedListener(element, event, handler) {
45 if (!handlerMap.has(element)) {
46 handlerMap.set(element, new Map());
47 }
48 handlerMap.get(element).set(event, handler);
49 element.addEventListener(event, handler);
50}
51
52function removeAllListeners(element) {
53 const handlers = handlerMap.get(element);
54 if (handlers) {
55 for (const [event, handler] of handlers) {
56 element.removeEventListener(event, handler);
57 }
58 handlerMap.delete(element);
59 }
60}
🔥

pro tip

WeakMap is 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 GC'd when the instance is no longer needed. This is also the pattern used internally by many JavaScript engines for private class fields (#privateField).
WeakSet — Garbage-Collectible 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. Like WeakMap, WeakSet has no iteration, no size, and only accepts object values. It is useful for tagging or marking objects without preventing their 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
11
12page1 = null; // page1 removed from visited automatically when GC'd
13// At this point visited.has(page1) would return false
14
15// Practical: prevent double-processing
16const processed = new WeakSet();
17
18function processItem(item) {
19 if (processed.has(item)) {
20 console.log("Already processed, skipping:", item.id);
21 return;
22 }
23 // ... process item ...
24 processed.add(item);
25 console.log("Processed:", item.id);
26}
27
28const items = [
29 { id: 1 }, { id: 2 }, { id: 1 } // duplicate
30];
31
32items.forEach(processItem);
33// Processed: 1
34// Processed: 2
35// Already processed, skipping: 1
36
37// Practical: marking objects for special handling
38const isAuthorized = new WeakSet();
39const isAdmin = new WeakSet();
40
41function grantAccess(user) {
42 isAuthorized.add(user);
43}
44
45function grantAdmin(user) {
46 isAuthorized.add(user);
47 isAdmin.add(user);
48}
49
50function checkAccess(user) {
51 return isAuthorized.has(user);
52}
53
54function checkAdmin(user) {
55 return isAdmin.has(user);
56}
57
58// When user objects are no longer referenced anywhere,
59// their membership in these sets is automatically cleaned up

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.
WeakRef — Holding Weak References

WeakRef (ES2021) is a lower-level primitive that holds a weak reference to an object. Unlike WeakMap/WeakSet which only store metadata, a WeakRef lets you hold a reference that does not prevent GC. You access the object via .deref(), which returns the object if it is still alive or undefined if it has been collected.

WeakRef is useful for caches, pools, and registries where retaining objects indefinitely would waste memory. However, use it carefully — garbage collection timing is non-deterministic and engine-specific. A value may stay alive much longer than expected, or be collected sooner.

weakref.js
JavaScript
1// WeakRef basics
2let target = { data: "important" };
3const ref = new WeakRef(target);
4
5// Access the value (may be collected)
6console.log(ref.deref()?.data); // "important"
7
8// Remove all strong references
9target = null;
10
11// At some later point (non-deterministic):
12// console.log(ref.deref()); // Could be the object or undefined
13
14// Practical: caching without preventing GC
15function createWeakCache() {
16 const cache = new Map(); // Consider using Map of WeakRefs
17
18 return {
19 get(key) {
20 const ref = cache.get(key);
21 if (ref) return ref.deref();
22 return undefined;
23 },
24 set(key, value) {
25 cache.set(key, new WeakRef(value));
26 },
27 has(key) {
28 return cache.has(key) && cache.get(key).deref() !== undefined;
29 }
30 };
31}
32
33// Practical: image cache that releases memory under pressure
34const imageCache = new Map();
35
36function fetchImage(url) {
37 const existing = imageCache.get(url);
38 if (existing) {
39 const img = existing.deref();
40 if (img) return Promise.resolve(img);
41 imageCache.delete(url);
42 }
43 return loadImage(url).then((img) => {
44 imageCache.set(url, new WeakRef(img));
45 return img;
46 });
47}

warning

Never depend on a WeakRef being deref'd to a specific value at a specific time. The garbage collector can run at any time (or never run). WeakRef is for optimization, not for correctness — your code must work correctly whether the object is collected or not. Always use ref.deref() ?? fallback pattern.
FinalizationRegistry

FinalizationRegistry (ES2021) lets you register a callback that is invoked when an object is garbage collected. This is useful for cleaning up external resources (file handles, network connections, native memory) when the JavaScript object is no longer reachable.

Like WeakRef, FinalizationRegistry is non-deterministic. The callback may never be called during the lifetime of your program, or it may be called long after the object is unreachable. Never rely on it for correctness — use it as a safety net, not as a primary cleanup mechanism.

finalization-registry.js
JavaScript
1// FinalizationRegistry basics
2const registry = new FinalizationRegistry((heldValue) => {
3 console.log("Object collected, held value:", heldValue);
4 // Perform cleanup based on heldValue
5});
6
7function createResource() {
8 const resource = { handle: "file-123" };
9 registry.register(resource, "cleanup-file-123");
10 return resource;
11}
12
13let res = createResource();
14res = null; // When GC runs, the callback will fire with "cleanup-file-123"
15
16// Practical: cleaning up native resources
17class NativeFileHandle {
18 constructor(path) {
19 this.path = path;
20 this.fd = openFileDescriptor(path); // native resource
21
22 // Register cleanup — if the user forgets to close()
23 this._cleanup = new FinalizationRegistry((fd) => {
24 console.log("Finalizer: closing file descriptor", fd);
25 closeFileDescriptor(fd);
26 });
27 this._cleanup.register(this, this.fd);
28 }
29
30 read() {
31 // Use this.fd
32 }
33
34 close() {
35 if (this.fd !== null) {
36 closeFileDescriptor(this.fd);
37 this.fd = null;
38 // Unregister to prevent double-close
39 this._cleanup.unregister(this);
40 }
41 }
42}
43
44// Usage — close() is the primary cleanup, FinalizationRegistry is backup
45function processFile(path) {
46 const file = new NativeFileHandle(path);
47 // If processFile throws and file is dropped, FD is still cleaned up eventually
48 const data = file.read();
49 file.close();
50 return data;
51}

danger

FinalizationRegistry callbacks are not guaranteed to run. They run in the microtask queue and may be delayed indefinitely. Never rely on them for correctness-critical operations like flushing buffers or releasing locks. Always provide explicit cleanup methods (like close()) and treat the finalizer as a safety net only.
Map vs Object Decision Guide

Choosing between Map and Object is a common design decision. Both store key-value pairs, but they have different characteristics. Here is a comprehensive comparison to guide your choice.

CriterionMapObject
Key typesAny (objects, functions, NaN)Strings and Symbols only
OrderInsertion order guaranteedInteger keys first, then insertion order for strings
sizeO(1) via .sizeO(n) via Object.keys().length
IterationDirectly iterable (for...of)Via Object.keys/values/entries
Performance (set/get)Excellent — specializedExcellent — highly optimized for V8
JSONNo direct supportNative (JSON.stringify/parse)
PrototypeNo prototype chainHas prototype (inherited keys)
Default keysNoneInherits from prototype (toString, etc.)
Spread[...map] gives entries{...obj} copies properties
Destructuring literalN/Aconst { key } = obj
map-vs-object.js
JavaScript
1// When to use Object:
2// 1. Fixed string keys known at author time
3const config = { host: "localhost", port: 3000 };
4
5// 2. JSON serialization needed
6const json = JSON.stringify(config);
7const parsed = JSON.parse(json);
8
9// 3. Destructuring pattern
10const { host, port } = config;
11
12// 4. Prototype-based property lookup
13const event = new Event("click");
14console.log(event.type); // "click" — inherited from prototype
15
16// When to use Map:
17// 1. Dynamic keys, especially non-string
18const userRoles = new Map();
19userRoles.set(alice, "admin");
20userRoles.set(bob, "user");
21
22// 2. Frequent addition/removal
23const cache = new Map();
24cache.set("key", value);
25cache.delete("key");
26
27// 3. Need reliable iteration or size
28console.log(cache.size);
29for (const [k, v] of cache) { /* ... */ }
30
31// 4. Map from another Map
32const copy = new Map(original);
33
34// 5. Keys are not known upfront (user data, etc.)
35const metrics = new Map();
36users.forEach((user) => {
37 metrics.set(user.id, computeMetrics(user));
38});
39
40// Performance: both are fast, but V8 can optimize
41// hash maps better when the shape is predictable.
42// For most cases, choose by API needs, not performance.

best practice

The simple rule: use Map when you need a dictionary with dynamic keys, especially non-string keys. Use Object when you have a fixed set of string keys known at author time, need JSON serialization, or want to use destructuring. If you're unsure, Map is almost always the right choice for modern code.
Performance Benchmarks

Understanding the relative performance characteristics of collection types helps make informed decisions. The benchmarks below represent typical operations on collections of various sizes. Results are engine-specific (V8 in Chrome/Node.js shown here).

OperationSmall (10 items)Medium (10K items)Large (1M items)Winner
Map.set/get~0.01ms~0.02ms~0.05msMap
Object set/get~0.01ms~0.03ms~0.15msMap (large sets)
Set.add/has~0.01ms~0.02ms~0.05msSet
Array.includes~0.01ms~1.2ms~120msSet (large)
Map iteration~0.005ms~0.3ms~4msMap
Object.keys iteration~0.005ms~0.4ms~5msComparable
Map delete~0.01ms~0.02ms~0.05msMap
Object delete~0.02ms~0.06ms~0.3msMap (larger sets)
performance.js
JavaScript
1// Performance test methodology (rough benchmarks)
2// These are not exact — results vary by engine, runtime, and data shape
3
4function benchmark(label, iterations, fn) {
5 const start = performance.now();
6 for (let i = 0; i < iterations; i++) fn(i);
7 const elapsed = performance.now() - start;
8 console.log(`${label}: ${elapsed.toFixed(2)}ms (${iterations} iterations)`);
9}
10
11// Sample benchmark: Map.get vs Object access
12const size = 100000;
13const map = new Map();
14const obj = {};
15
16for (let i = 0; i < size; i++) {
17 map.set(i, i);
18 obj[i] = i;
19}
20
21// Warmup
22for (let i = 0; i < 1000; i++) {
23 map.get(i); obj[i];
24}
25
26// Benchmark
27benchmark("Map get", 100000, (i) => map.get(i));
28benchmark("Object access", 100000, (i) => obj[i]);
29
30// Set vs Array.includes
31const arr = Array.from({ length: 10000 }, (_, i) => i);
32const set = new Set(arr);
33const target = 9999;
34
35benchmark("Array.includes", 10000, () => arr.includes(target));
36benchmark("Set.has", 10000, () => set.has(target));
37// Set.has is dramatically faster for large collections (~O(1) vs ~O(n))
38
39// Key takeaway: for lookups on >1000 items, use Set/Map over Array/Object
🔥

pro tip

For small collections (under 100 items), the performance difference between Object and Map is negligible — choose based on API convenience. For large collections (10,000+ items), Map and Set are significantly faster for insertions, deletions, and lookups. Array methods like includes() and indexOf() become dramatically slower as size grows — use Set instead.
Best Practices
Use Map for dynamic dictionaries — especially with non-string keys or frequent additions/removals
Use Object for fixed-string-key records, JSON-serialized data, and when you need prototype chain lookup
Use Set for uniqueness enforcement and fast membership testing — set.has() is O(1) vs array.includes() O(n)
Use WeakMap for private data attached to specific object instances — prevents memory leaks when instances are GC'd
Use WeakSet for marking/tagging objects without preventing GC — ideal for visited, processed, or authorized tracking
Use WeakRef only for optimization caches where losing the reference is acceptable — never for correctness-critical data
Use FinalizationRegistry as a safety net for native resource cleanup, not as the primary cleanup mechanism
Always provide an explicit cleanup method (like .close() or .dispose()) alongside FinalizationRegistry
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
collections-quickref.js
JavaScript
1// Quick reference
2
3// Create
4const map = new Map([["key", "value"]]);
5const set = new Set([1, 2, 3]);
6const wm = new WeakMap();
7const ws = new WeakSet();
8
9// Map operations
10map.set(key, val); // Add/update
11map.get(key); // Read
12map.has(key); // Check
13map.delete(key); // Remove
14map.size; // Count
15map.clear(); // Empty
16
17// Set operations
18set.add(val); // Add
19set.has(val); // Check
20set.delete(val); // Remove
21set.size; // Count
22set.clear(); // Empty
23
24// WeakMap/WeakSet
25wm.set(obj, val); // Add (obj key only)
26wm.get(obj); // Read
27wm.has(obj); // Check
28wm.delete(obj); // Remove
29
30// Conversions
31const asArray = [...map]; // Map → [key,value][]
32const fromArray = new Map(asArray); // [key,value][] → Map
33const asObject = Object.fromEntries(map); // Map → Object
34const asMap = new Map(Object.entries(obj)); // Object → Map
35
36// Deduplicate
37const unique = [...new Set(array)];
38
39// Fast membership
40const lookup = new Set(items);
41if (lookup.has(target)) { /* O(1) */ }

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 and constraints without benefit.
$Blueprint — Engineering Documentation·Section ID: JS-COLLECT·Revision: 1.0