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

JS set.js
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]);
Output
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

JS unique.js
const visits = ["home", "about", "home", "blog", "about"];
const uniquePages = [...new Set(visits)];
console.log(uniquePages);
Output
[ 'home', 'about', 'blog' ]

Set operations

Sets have built-in methods for the classic operations from maths:

JS set-ops.js
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));
Output
[ '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

JS map.js
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}`);
}
Output
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:

JS object-keys.js
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));
Output
[ '[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

JS counting.js
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);
Output
3 [ 'the', 3 ]

Map or object?

ObjectMap
Key typesStrings and symbolsAnything
SizeObject.keys(o).lengthmap.size
IterationVia Object.entriesDirectly with for…of
Frequent adds and deletesOKOptimised for it
JSON supportBuilt inConvert first: Object.fromEntries(map)
Best forFixed-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.

Try it yourself

Try it yourself
Console output