Map and Set
Why it matters
Objects and arrays cover most tasks, but not all. Sometimes you need a dictionary whose keys can be
more than strings, or fast counting matters. And sometimes you need a collection without repeats:
unique visitors, a list of tags, checking "have we seen this before". JavaScript has two collections for
this — Map and Set.
Set — a collection of unique values
const tags = new Set();
tags.add("js");
tags.add("css");
tags.add("js"); // a repeat — ignored
console.log(tags.size);
console.log(tags.has("css"));
tags.delete("css");
console.log([...tags]);
Output:
2
true
["js"]
A Set can be created straight from an array — the repeats disappear:
const visits = ["Anna", "Timur", "Anna", "Ira", "Timur"];
const people = new Set(visits);
console.log(people.size);
console.log([...people]);
Output:
3
["Anna","Timur","Ira"]
[...set] turns the set back into an array. The order is the order in which values were added.
Looping over a Set
for (const tag of new Set(["a", "b"])) {
console.log(tag);
}
Output:
a
b
Map — a dictionary
A Map stores "key → value" pairs, like an object, but:
- the key can be anything: a number, an object, even a function (an object's keys are only strings);
- the size is the
sizeproperty; - there are no "built-in" keys like
constructorthat every object has; - the key order always equals the insertion order.
const stock = new Map();
stock.set("apple", 10);
stock.set("pear", 4);
stock.set("apple", 12); // overwrite
console.log(stock.get("apple"));
console.log(stock.get("plum")); // no such key
console.log(stock.has("pear"), stock.size);
stock.delete("pear");
console.log([...stock]);
Output:
12
undefined
true 2
[["apple",12]]
A Map can be created straight from an array of pairs: new Map([["a", 1], ["b", 2]]).
Looping over a Map
const prices = new Map([["tea", 50], ["coffee", 90]]);
for (const [name, price] of prices) {
console.log(`${name}: ${price}`);
}
console.log([...prices.keys()], [...prices.values()]);
Output:
tea: 50
coffee: 90
["tea","coffee"] [50,90]
Each Map element in a loop is a [key, value] pair, handy to unpack with destructuring.
Object ↔ Map
const obj = { a: 1, b: 2 };
const map = new Map(Object.entries(obj));
const back = Object.fromEntries(map);
console.log(map.get("b"), back);
Output:
2 {"a":1,"b":2}
What to choose?
| Task | Use |
|---|---|
| Describing one thing (a user, a product) | an object {} |
| A list in order | an array [] |
| Counting, a dictionary with arbitrary keys, frequent adds/deletes | Map |
| Uniqueness, a fast "is it there" check | Set |
How it works inside
Set and Map are built as a hash table: from the key's value the place to look for it is computed
immediately. So set.has(x) works instantly even for a million elements. array.includes(x) goes through
the array from the start — for a big array and many checks that's thousands of times slower. If in a loop
you check "is the element in another array", first make a Set from that array.
Uniqueness in a Set is checked like === (except NaN, which is considered equal to itself). So two
different objects { a: 1 } both stay in a Set — they're different references.
Common mistakes
- Square brackets for a Map.
map["a"] = 1creates an ordinary property, not a Map element:map.get("a")returnsundefined. Usesetandget. lengthinstead ofsize. Map and Set havesize.- Expecting a
Setto remove equal objects.new Set([{a: 1}, {a: 1}]).size→2. - Forgot to convert to an array. A Set has no
maporfilter— first[...set]. includesin a loop over big arrays. Slow. Make aSetand usehas.
Cheat sheet
| Set | Map |
|---|---|
new Set(arr) |
new Map([[k, v], …]) |
s.add(x) |
m.set(k, v) |
s.has(x) |
m.has(k), m.get(k) |
s.delete(x) |
m.delete(k) |
s.size |
m.size |
[...s] — to an array |
[...m] — an array of pairs |
for (const x of s) |
for (const [k, v] of m) |