Map, Set, WeakMap & WeakSet
Map
Map holds key-value pairs like a plain object, but keys can be any type (not just strings/symbols), insertion order is preserved, and size is tracked directly.
const scores = new Map();
scores.set("Ada", 95);
scores.set("Grace", 88);
const objKey = { id: 1 };
scores.set(objKey, "value keyed by an object"); // objects work as keys, unlike plain objects
console.log(scores.get("Ada")); // 95
console.log(scores.has("Grace")); // true
console.log(scores.size); // 3
scores.delete("Grace");
console.log(scores.size); // 2
for (const [key, value] of scores) {
console.log(key, value);
}
// Ada 95
// {id: 1} "value keyed by an object"
// (Grace was removed by the delete() above)
console.log([...scores.keys()]); // ["Ada", objKey]
console.log([...scores.values()]); // [95, "value keyed by an object"]
console.log([...scores.entries()]); // [["Ada", 95], [objKey, "value keyed by an object"]]
scores.forEach((value, key) => console.log(key, value));
scores.clear();
console.log(scores.size); // 0
// Map vs Object — plain objects inherit properties/methods from Object.prototype,
// so a key like "toString" can silently overwrite a real method
const obj = {};
obj["toString"] = "gotcha"; // overwrites the inherited toString method
console.log(obj.toString()); // TypeError: obj.toString is not a function
const map = new Map();
map.set("toString", "no collision");
console.log(map.get("toString")); // "no collision"
console.log(typeof map.toString); // "function" — Map's own data never touches its prototype methods
// Converting between Map, Object, and Array
const obj = { name: "Ada", age: 36 };
const mapFromObj = new Map(Object.entries(obj));
console.log(mapFromObj.get("name")); // "Ada"
const objFromMap = Object.fromEntries(mapFromObj);
console.log(objFromMap); // { name: "Ada", age: 36 }
const arrayFromMap = Array.from(mapFromObj); // same as [...mapFromObj]
console.log(arrayFromMap); // [["name", "Ada"], ["age", 36]]
// Gotcha: object keys are compared by reference (identity), not shape
const map = new Map();
map.set({ id: 1 }, "first");
map.set({ id: 1 }, "second"); // a *different* object, even though it looks identical
console.log(map.size); // 2 — two distinct keys, not merged
// NaN is the one primitive that behaves specially: Map uses SameValueZero,
// so NaN is treated as equal to itself (unlike ===)
map.set(NaN, "not a number");
console.log(map.get(NaN)); // "not a number"
Set
Set stores a collection of unique values of any type — duplicates are automatically ignored.
const ids = new Set([1, 2, 2, 3, 3, 3]);
console.log(ids); // Set(3) {1, 2, 3}
console.log(ids.size); // 3
ids.add(4);
ids.has(2); // true
ids.delete(1);
console.log([...ids]); // [2, 3, 4] — spread converts back to an array
ids.forEach((value) => console.log(value)); // 2, 3, 4
ids.clear();
console.log(ids.size); // 0
// Common use: deduplicate an array
const unique = [...new Set([1, 1, 2, 2, 3])];
console.log(unique); // [1, 2, 3]
New in modern JS engines:
Setalso has composition methods —union(),intersection(),difference(),symmetricDifference(),isSubsetOf(),isSupersetOf(), andisDisjointFrom()— for combining sets without manually looping.const a = new Set([1, 2, 3]); const b = new Set([2, 3, 4]); console.log(a.union(b)); // Set(4) {1, 2, 3, 4} console.log(a.intersection(b)); // Set(2) {2, 3} console.log(a.difference(b)); // Set(1) {1}
WeakMap & WeakSet
WeakMap and WeakSet are similar to Map/Set, but with key differences:
- Keys (for
WeakMap) or values (forWeakSet) must be objects, not primitives. - References are weak — if there’s no other reference to the object, it can be garbage-collected, and its entry is removed automatically.
- Not iterable, and have no
.size— because entries can disappear at any time, enumerating them wouldn’t be reliable.
let user = { name: "Ada" };
const metadata = new WeakMap();
metadata.set(user, { lastLogin: "2026-07-12" });
console.log(metadata.get(user)); // { lastLogin: "2026-07-12" }
user = null; // no other references to the object left
// The entry in `metadata` becomes eligible for garbage collection
// automatically — a regular Map would have kept it alive forever.
This makes WeakMap ideal for attaching private/extra data to objects (e.g. DOM nodes, class instances) without causing memory leaks.
WeakSet works the same way, but as a plain collection of objects rather than key-value pairs — commonly used to track “has this object already been processed?” without preventing garbage collection.
const processedNodes = new WeakSet();
function processNode(node) {
if (processedNodes.has(node)) {
return; // already handled, skip
}
processedNodes.add(node);
console.log("Processing:", node);
}
let el = document.createElement("div");
processNode(el); // "Processing: <div>"
processNode(el); // skipped, already in the set
el = null; // once no other references exist, the WeakSet entry is
// automatically eligible for garbage collection too.