Sets & Maps
Arrays and plain objects cover most needs, but two other built-in collections are often a better fit. A Set stores unique values with fast membership checks. A Map stores key–value pairs where the keys can be anything, not just strings.
Set: a collection of unique values
const tags = new Set(["js", "web", "js", "css"]); console.log(tags); console.log(tags.size); tags.add("html"); tags.add("web"); console.log(tags.has("css"), tags.has("python")); tags.delete("css"); console.log([...tags]);
Set(3) { 'js', 'web', 'css' }
3
true false
[ 'js', 'web', 'html' ]Duplicates are ignored automatically. has is very fast even for huge sets, much faster than array.includes on a large array. Sets keep insertion order and are iterable with for…of.
Removing duplicates from an array
const visits = ["home", "about", "home", "blog", "about"]; const uniquePages = [...new Set(visits)]; console.log(uniquePages);
[ 'home', 'about', 'blog' ]
Set operations
Sets have built-in methods for the classic operations from maths:
const frontend = new Set(["html", "css", "js"]); const backend = new Set(["js", "sql", "python"]); console.log([...frontend.union(backend)]); console.log([...frontend.intersection(backend)]); console.log([...frontend.difference(backend)]); console.log(new Set(["css"]).isSubsetOf(frontend));
[ 'html', 'css', 'js', 'sql', 'python' ] [ 'js' ] [ 'html', 'css' ] true
These methods (union, intersection, difference, symmetricDifference, isSubsetOf…) arrived in all major browsers in 2024.
Map: key–value pairs with any keys
const stock = new Map(); stock.set("apples", 12); stock.set("pears", 0); stock.set("plums", 7); console.log(stock.get("apples")); console.log(stock.has("kiwis"), stock.get("kiwis")); console.log(stock.size); stock.delete("pears"); for (const [fruit, count] of stock) { console.log(`${fruit}: ${count}`); }
12 false undefined 3 apples: 12 plums: 7
Create a map pre-filled from an array of pairs: new Map([["a", 1], ["b", 2]]), or from an object with new Map(Object.entries(obj)).
Objects as keys
The big advantage over plain objects: map keys can be any value. Object keys are always converted to strings, which breaks when you try to use objects as keys:
const userA = { name: "Ana" }; const userB = { name: "Ben" }; const plain = {}; plain[userA] = "first"; plain[userB] = "second"; console.log(Object.keys(plain), plain[userA]); const visits = new Map(); visits.set(userA, 3); visits.set(userB, 5); console.log(visits.get(userA), visits.get(userB));
[ '[object Object]' ] second 3 5
Both objects became the same string key "[object Object]" in the plain object, so the second assignment overwrote the first. The Map keeps them separate.
Counting with a Map
const words = "the cat and the hat and the bat".split(" "); const counts = new Map(); for (const w of words) { counts.set(w, (counts.get(w) ?? 0) + 1); } const top = [...counts].toSorted((a, b) => b[1] - a[1])[0]; console.log(counts.get("the"), top);
3 [ 'the', 3 ]
Map or object?
| Object | Map | |
|---|---|---|
| Key types | Strings and symbols | Anything |
| Size | Object.keys(o).length | map.size |
| Iteration | Via Object.entries | Directly with for…of |
| Frequent adds and deletes | OK | Optimised for it |
| JSON support | Built in | Convert first: Object.fromEntries(map) |
| Best for | Fixed-shape records (a user, a config) | Dynamic lookups, caches, counters |
WeakMap and WeakSet
WeakMap and WeakSet only accept objects as keys, and don't stop those objects from being garbage-collected. They're used to attach extra data to objects you don't own — such as DOM elements — without causing memory leaks. They can't be iterated or counted.