Modules, Map, Set and Generators · Lesson 32 of 40
Map, Set, WeakMap and Symbol
Objects and arrays can do almost anything, but they aren't always the right tool. Map is a proper key-value dictionary; Set is a collection of unique values. They're faster and safer for those jobs, and knowing when to reach for them is a sign of an experienced developer.
In this lesson
- Map: any key type, insertion order, size
- Set: unique values and set operations
- WeakMap and WeakSet for attaching data to objects
- Symbols as unique keys
Map
const stock = new Map();
stock.set("pen", 120).set("notebook", 45).set("bag", 0); // set() is chainable
console.log(stock.get("pen"), stock.has("bag"), stock.size);
stock.delete("bag");
console.log([...stock.keys()]);
for (const [item, qty] of stock) {
console.log(`${item}: ${qty}`);
}
const prices = new Map(Object.entries({ pen: 20, notebook: 60 })); // object → Map
console.log(Object.fromEntries(prices)); // Map → object
120 true 3
[ 'pen', 'notebook' ]
pen: 120
notebook: 45
{ pen: 20, notebook: 60 }Any value can be a key
const asha = { id: 1, name: "Asha" };
const ravi = { id: 2, name: "Ravi" };
const lastSeen = new Map();
lastSeen.set(asha, "10:42").set(ravi, "09:15");
console.log(lastSeen.get(asha));
const obj = {};
obj[asha] = "10:42"; // an object key is converted to a string…
obj[ravi] = "09:15"; // …so both become "[object Object]"
console.log(Object.keys(obj), obj[asha]);
10:42 [ '[object Object]' ] 09:15
| Object | Map | |
|---|---|---|
| Key types | strings and symbols | anything – objects, functions, numbers |
| Order | insertion, but integer keys first | always insertion order |
| Size | Object.keys(o).length | map.size |
| Inherited keys | yes (e.g. "toString", "__proto__") | none |
| Frequent add/remove | slower | optimised for it |
| JSON | direct | convert with Object.fromEntries or [...map] |
| Best for | fixed-shape records (a user, a config) | dictionaries, caches, lookups by ID |
Set
const visitors = new Set();
for (const id of [101, 205, 101, 330, 205]) visitors.add(id); // duplicates ignored
console.log(visitors.size, visitors.has(330), [...visitors]);
const tags = new Set(["js", "sql", "js", "react"]);
tags.delete("sql");
console.log(tags);
3 true [ 101, 205, 330 ]
Set(2) { 'js', 'react' }set.has(x) is close to O(1); array.includes(x) is O(n). For large lists you check often, a Set is dramatically faster.
Set operations
const javaDevs = new Set(["Asha", "Ravi", "Meera"]);
const jsDevs = new Set(["Ravi", "Meera", "Kiran"]);
const both = javaDevs.intersection(jsDevs);
const either = javaDevs.union(jsDevs);
const onlyJava = javaDevs.difference(jsDevs);
console.log([...both], [...either], [...onlyJava]);
console.log(new Set(["Ravi"]).isSubsetOf(jsDevs));
// The same without the newer methods (works everywhere):
const bothManual = [...javaDevs].filter((d) => jsDevs.has(d));
console.log(bothManual);
[ 'Ravi', 'Meera' ] [ 'Asha', 'Ravi', 'Meera', 'Kiran' ] [ 'Asha' ] true [ 'Ravi', 'Meera' ]
Set methods (ES2025)
- unionin either
- intersectionin both
- differencein A, not B
- symmetricDifferencein exactly one
- isSubsetOfall of A in B?
- isDisjointFromnothing shared?
WeakMap and WeakSet
A WeakMap holds its object keys "weakly": when nothing else references the key object, it (and its value) can be garbage-collected. That makes WeakMap ideal for attaching extra data to objects you don't own – DOM elements, objects from a library – without causing memory leaks.
const clickCounts = new WeakMap();
function trackClick(element) {
clickCounts.set(element, (clickCounts.get(element) ?? 0) + 1);
return clickCounts.get(element);
}
const button = { id: "buy" }; // stands in for a DOM element
trackClick(button);
console.log(trackClick(button));
// When the button is removed and no longer referenced, its entry disappears automatically.
2
WeakMap keys must be objects, and a WeakMap can't be looped over or sized – by design, since entries may vanish at any time. WeakSet is the same idea for "has this object been seen?" checks.
Symbols
A Symbol is a unique, primitive value. Two symbols are never equal, even with the same description, which makes them perfect as property keys that can't clash with anyone else's:
const id = Symbol("id");
const user = { name: "Asha", [id]: 9001 };
console.log(user[id], Symbol("id") === Symbol("id"));
console.log(Object.keys(user), JSON.stringify(user)); // symbol keys are hidden
// Well-known symbols customise built-in behaviour
const range = {
from: 1, to: 4,
[Symbol.iterator]: function* () { for (let i = this.from; i <= this.to; i++) yield i; },
};
console.log([...range]);
9001 false
[ 'name' ] {"name":"Asha"}
[ 1, 2, 3, 4 ]Symbol.iterator is how for...of and spread work – the next lesson builds on it.
Practice
Try it yourself
Find the first repeated character in "programming" using a Set.
Show solution
function firstRepeat(str) { const seen = new Set(); for (const ch of str) { if (seen.has(ch)) return ch; seen.add(ch); } return null; } console.log(firstRepeat("programming"), firstRepeat("abc"));r null
Count word frequency with a Map and print the most common word.
Show solution
const words = "the cat and the hat and the bat".split(" "); const freq = new Map(); for (const w of words) freq.set(w, (freq.get(w) ?? 0) + 1); const [top, count] = [...freq].reduce((best, e) => (e[1] > best[1] ? e : best)); console.log(top, count);the 3
Quick check
When should you choose a Map over an object?
For dictionaries with dynamic or non-string keys, frequent additions/removals, or when you need size and guaranteed order.
How do you remove duplicates from an array?
[...new Set(arr)]
Why can't you loop over a WeakMap?
Its entries can be garbage-collected at any moment, so enumeration would be unreliable.
Summary
- Map: any key type, insertion order,
size– best for dictionaries and caches. - Set: unique values, fast
has, and union/intersection/difference. - WeakMap attaches data to objects without leaks; Symbols create clash-free keys.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.