Topic 14.3
Map and Set
In one line
Map is a key-value collection whose keys can be any value (objects included) and which remembers insertion order; Set is a collection of unique values. Both are iterable, have a real size, and are built for frequent adding, looking up and removing.
Think of it like this
A coat check and a guest list. At a coat check you hand over a coat (the key) and get a ticket back pointing to its hook (the value); any kind of coat works, and the coat itself is the key. That's a Map. A guest list only records who is coming; writing a name twice doesn't make two guests. That's a Set.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Map
- A collection of key-value pairs where the key can be any value, kept in the order you added them.
- Set
- A collection where each value appears at most once.
- Entry
- One key-value pair in a Map, written as a two-item array
[key, value]. - By reference
- Two objects count as the same key only if they are the very same object, not just alike.
- SameValueZero
- The comparison Maps and Sets use: like
===, butNaNmatchesNaN. - Insertion order
- The order in which items were added. Maps and Sets loop in this order.
- Union / intersection
- Everything in either set / only what is in both sets.
- Difference
- What is in the first set but not in the second.
Step by step
01Create and fill a Map
Start empty and set entries, or pass an array of pairs. set returns the map, so calls chain.
const prices = new Map([
["tea", 30],
["samosa", 15],
]);
prices.set("juice", 50).set("tea", 35); // tea replaced
console.log(prices.get("tea"), prices.size); // 35 302Use objects as keys
Attach data to an object without touching it, for example how many times each DOM element was clicked. With a plain object as the store, every element would become the same key "[object Object]".
const clicks = new Map();
for (const button of document.querySelectorAll("button")) {
button.addEventListener("click", () => {
clicks.set(button, (clicks.get(button) ?? 0) + 1);
});
}03Count and group
Counting with a Map is a pattern you'll use in /dsa constantly (frequency maps, two-sum, anagrams): counts.set(x, (counts.get(x) ?? 0) + 1).
04Remove duplicates with a Set
Pass any iterable to new Set() and spread it back to an array. Order of first appearance is kept.
const unique = [...new Set([3, 1, 3, 2, 1])];
console.log(unique); // [ 3, 1, 2 ]05Combine sets
The ES2025 methods read like the maths. Each returns a new Set.
06Choose the right collection
Fixed record with string keys → object. Growing dictionary, non-string keys, order or size → Map. Ordered list with duplicates → array. Membership checks and uniqueness → Set.
Try it yourself
- 1
Find the object key again
In the first example,
m.get({ id: 1 })gaveundefined. Change it so it finds"an object"without changing the Map. (Hint: which variable holds the exact object?) - 2
Count letters
In the second example, count the letters of
"banana"instead of words. Predict the printed Map before you run it: what order will the keys be in? - 3
Which tags are new?
In the third example, make
const old = new Set(["js", "css"])and log the tags that are intagsbut not inold. Which method do you need?
Code & diagrams
`{ id: 1 }` written again is a new object, so the Map doesn't find it. In the plain object, `1` became `"1"` and both objects became `"[object Object]"`, so `b` overwrote `a`.
Preview
Console
Nothing logged yet.Expected console output
4
number one | string one
an object
undefined
Map(3) { '1' => 'string one', 1 => 'number one', { id: 1 } => 'an object' }
[ '1', '[object Object]' ]
object b`Map.groupBy` arrived in ES2024; the rest is ES2015. Everything comes out in insertion order.
Preview
Console
Nothing logged yet.Expected console output
[ 'tea', 'samosa', 'juice' ]
{ tea: 30, samosa: 15, juice: 50 }
Map(2) { 'a' => 1, 'b' => 2 }
Map(5) { 'the' => 3, 'cat' => 1, 'and' => 2, 'hat' => 1, 'bat' => 1 }
[ 'Pune', 'Delhi' ]
[ 'Asha', 'Meera' ]The set operation methods (`union` and friends) are ES2025; the last line is the older way to intersect. Two look-alike objects are still two values.
Preview
Console
Nothing logged yet.Expected console output
Set(3) { 'js', 'css', 'html' }
3 true
[ 3, 1, 2 ]
Set(2) { NaN, 0 }
2
Set(5) { 1, 2, 3, 4, 5 }
Set(2) { 3, 4 }
Set(2) { 1, 2 }
Set(3) { 1, 2, 5 }
true true
Set(2) { 3, 4 }Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use brackets instead of set()
Write const m = new Map(); m["a"] = 1; console.log(m.get("a"), m.size);
Break #2
Send a Map as JSON
Write console.log(JSON.stringify(new Map([["a", 1]])));
Myth vs fact
Myth
A Set removes duplicate objects that look the same.
Fact
Objects are compared by reference. new Set([{ a: 1 }, { a: 1 }]) has size 2. To dedupe records, key them by an id in a Map.
Myth
Maps are always faster than objects.
Fact
Maps are designed for frequent additions and deletions and large dynamic key sets. For small records with fixed shapes, engines optimise objects very well. Pick by meaning first.
Myth
Object keys keep the order you add them, so objects are as good as Maps for order.
Fact
Objects list integer-like keys first in ascending order, then string keys in insertion order. Maps keep pure insertion order for every key type.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The spec requires Map and Set operations to be sublinear on average; engines use hash tables. V8 uses a deterministic ordered hash table (after Tyler Close's design), which keeps insertion order cheap and makes iteration fast.
- ▸
Deleting from a Map or Set while iterating it is safe and well defined: deleted entries that haven't been visited are skipped, and entries added during iteration are visited. Arrays have no such guarantee for
spliceinsideforEach. - ▸
Set methods accept any set-like argument (an object with
size,hasandkeys), soa.intersection(map)works with a Map's keys. They do not accept arrays; wrap them withnew Set(array)first. - ▸
Maps don't keep
-0:map.set(-0, x)stores the key as+0, and[...map.keys()]gives0. This is the one place SameValueZero shows a difference fromObject.is.
Remember this
- 1
A Map stores entries (key-value pairs).
new Map()makes an empty one;map.set(key, value)adds or replaces (and returns the map, so calls can chain);map.get(key)reads (orundefined);map.has(key),map.delete(key),map.clear()and themap.sizeproperty do what they say. You can start it from any iterable of pairs:new Map([["tea", 30], ["pen", 15]]), ornew Map(Object.entries(obj)). - 2
The big difference from an object: keys of any type. Object property keys are always strings (or symbols):
obj[1]andobj["1"]are the same property, and using an object as a key turns it into the string"[object Object]", so every object collides. A Map keeps keys as they are:1and"1"are different keys, and an object key is matched by reference (the exact same object, not one that looks the same). Maps also have no inherited keys, so a key called"__proto__"or"toString"is safe. - 3
Keys are compared with SameValueZero: like
===, exceptNaNequalsNaN(soNaNworks as a key), and0and-0count as the same. Maps iterate in insertion order.for (const [key, value] of map)loops over entries, andmap.keys(),map.values(),map.entries()andmap.forEach((value, key) => ...)give the parts. Convert back withObject.fromEntries(map)or[...map].Map.groupBy(items, fn)(ES2024) groups an array into a Map. - 4
A Set stores unique values, compared with the same SameValueZero rule.
set.add(v),set.has(v),set.delete(v),set.clear(),set.size. Adding a value that's already there does nothing. The most famous use is removing duplicates:[...new Set(array)]. Two different objects with the same contents are still two values, because objects are compared by reference. - 5
Sets now have set operations built in (ES2025):
a.union(b)(in either),a.intersection(b)(in both),a.difference(b)(inabut notb),a.symmetricDifference(b)(in exactly one), plus the yes/no checksa.isSubsetOf(b),a.isSupersetOf(b)anda.isDisjointFrom(b)(no values in common). Each returns a new Set and doesn't changea. In older code you'll see the same done with spread andfilter:new Set([...a].filter((x) => b.has(x))). - 6
When to use which: use an object for a record with fixed, known string keys (a user's
name,email), and for JSON. Use a Map for a dictionary that grows and shrinks at runtime, has non-string keys, or needs order andsize. Use an array for an ordered list where duplicates and positions matter, and a Set when you mostly ask "is this in it?":set.has(x)is fast on average however big the set gets, whilearray.includes(x)checks items one by one. One catch:JSON.stringifyturns a Map or Set into{}, so convert first.
Explain it without notes
When would you choose a Map over a plain object?
How do Map and Set compare keys and values? What happens with NaN and with objects?
How do you remove duplicates from an array, and what are the limits of that method?
Name the ES2025 set methods and say what each returns.
Practice
Write firstRepeat(list) that returns the first value that appears a second time, using a Set. firstRepeat(["a", "b", "c", "b", "a"]) should return "b".
Given const orders = [["tea", 2], ["pen", 1], ["tea", 3]], build a Map of total quantity per item and log it.
Two classes share some students: const x = new Set(["Asha", "Ravi", "Meera"]) and const y = new Set(["Ravi", "Kabir"]). Log the students in both, and everyone in either.
Trade-offs
- ↔
Maps and Sets give any-type keys, real order and fast membership, but they don't serialise to JSON and don't work with object destructuring or dot access, so they cost a conversion at API boundaries.
- ↔
array.includesis fine for a handful of items and keeps duplicates and order; a Set pays a little memory and setup time to make repeatedhaschecks fast on large collections.
Done when you can
I can create, read, update, delete and loop over a Map.
I can explain why objects make poor dictionaries for non-string keys.
I can dedupe an array with a Set and know its limit with objects.
I can use
union,intersection,differenceand the subset checks.I can convert between Map, object, array and JSON.
I can choose between object, Map, array and Set for a job.