8. Map and Set
Looking something up in an array means searching it. A Map finds the answer almost directly, whatever the key and however much it holds. The trick behind it, hashing, is one you’ll build yourself.
- Store and look up values by key with a Map, including object keys.
- Keep a collection of unique values with a Set.
- Loop over arrays, Maps and Sets with for...of.
- Explain, in pictures, how a hash table finds a key without checking every entry.
Remembering things about objects
You want to note which particles have already been hit. An object seems like a good place to keep notes, with bracket access from chapter 3:
const hit = {};
const a = { id: 1 };
const b = { id: 2 };
hit[a] = true;
console.log(hit[b]); // true ?!
b was never hit, yet the answer is true. A property name is always a
string, so JavaScript first turns a into a string, and every plain
object turns into the same string: '[object Object]'. So hit[a] and
hit[b] are the same property. The same thing happens with numbers:
hit[1] and hit['1'] are one property too.
What you need is a lookup table whose keys can be anything, kept exactly as they are.
A Map is a two-column table. On the left go the keys, on the right the values. You find a row by its key, and the key can be any value at all: a string, a number, even an object.
Map
Map is a class, so you make one with new and use its methods:
const ages = new Map();
ages.set('ada', 36);
ages.set('alan', 41);
console.log(ages.get('ada')); // 36
console.log(ages.get('bob')); // undefined
console.log(ages.has('alan')); // true
console.log(ages.size); // 2
ages.delete('alan');
Object keys are compared by identity, exactly like === in chapter 4. So
the particle problem goes away:
const hit = new Map();
hit.set(a, true);
console.log(hit.has(a)); // true
console.log(hit.has(b)); // false
console.log(hit.has({ id: 1 })); // false
The last line is a lookalike, a different object, so it isn’t found.
A Map holds key → value entries[1]. set(k, v) adds
an entry, or replaces the value if k is already there. get(k) returns
the value, or undefined if there is none. has(k) asks whether k is
there, delete(k) removes it, and size counts the entries. Keys can be
any value. They are compared like ===, so objects match only themselves.
Each key appears at most once.
Here is the most common Map job of all, counting:
const cs = ['red', 'blue', 'red'];
const counts = new Map();
for (let i = 0; i < cs.length; i++) {
const c = cs[i];
if (counts.has(c)) {
counts.set(c, counts.get(c) + 1);
} else {
counts.set(c, 1);
}
}
console.log(counts);
// Map(2) { 'red' => 2, 'blue' => 1 }
for…of
Notice the counting loop only uses i to fetch cs[i]. When you don’t
care about the index, there’s a shorter loop.
A for loop with i says “for each slot number, look in that slot”. A
for...of loop says “for each thing in here”. You get the things
directly.
for (const c of ['red', 'blue']) {
console.log(c);
}
for (const entry of counts) {
console.log(entry[0], entry[1]);
}
// red 2
// blue 1
Looping over a Map gives you each entry as a two-slot array:
[key, value]. Entries come out in the order their keys were first
added[1]. You’ll often see this written
for (const [k, v] of counts), a shorthand that unpacks the two slots into
the names k and v.
for (const x of things) runs the body once for each value in
things, in order, with x holding that value[3].
It works on arrays, strings, Maps and Sets. It doesn’t work on plain
objects, and it doesn’t give you the index: if you need i, use the
classic for loop.
Set
Sometimes there’s no value to store: you only want to know whether something is in the collection. That’s a Set.
const tags = new Set();
tags.add('fast');
tags.add('red');
tags.add('fast'); // already there
console.log(tags.size); // 2
console.log(tags.has('red')); // true
const ids = new Set([3, 1, 3, 3, 2]);
console.log(ids); // Set(3) { 3, 1, 2 }
A Set is a collection of unique values[2]. add(v)
puts v in, and does nothing if it’s already there. has(v) asks whether
it’s there, delete(v) removes it, and size counts the values. It uses
the same idea of “the same value” as Map keys, and new Set(array) is the
quickest way to drop duplicates.
const a = { id: 1 };
const b = { id: 2 };
const hp = new Map();
hp.set(a, 100);
hp.set(b, 40);
hp.set(a, 90);
console.log(hp.get(a), hp.size);
console.log(hp.get({ id: 1 }));
for (const e of hp) {
console.log(e[0].id, e[1]);
}
const s = new Set([3, 1, 3, 2, 1]);
console.log(s, s.has(2));How can a lookup be fast?
To find 'pink' in an array of a million names, a loop checks name after
name, a million in the worst case. A Map gets there almost directly,
however many entries it holds. How?
Think of a post room with a wall of pigeonholes. Each letter’s address decides its pigeonhole. To find a letter, you don’t read every letter in the building: you work out which pigeonhole it belongs in, and look only there.
// toy hash: (number of letters) % 4m.set('red', 2);m.set('blue', 1);m.set('green', 5);m.set('pink', 7);m.get('green');m.get('pink');m.get('gold');
Hashing, the intuition: a hash function turns a key into a number, and that number picks a bucket. Storing and finding a key both look only inside its bucket. When keys spread evenly across enough buckets, each bucket holds just a few entries, so a lookup stays quick however big the table grows. A table organised like this is a hash table.
The JavaScript standard doesn’t say how Map and Set must be built. It
only requires that, on average, a lookup’s cost grows more slowly than the
number of entries[1]. V8 builds Map, Set, WeakMap
and WeakSet on hash tables. For an object key, V8’s hash code is simply a
random number, unrelated to what the object contains, so it can’t be
recomputed and has to be stored with the object[4].
That fits Map’s rule that objects are compared by identity, not contents.
You’ll build a hash table from scratch in chapter 51, and your virtual machine will keep its global variables in one. Engines use them for ordinary objects too: V8 switches an object’s property storage to a dictionary, a hash table, once it holds a great many properties[4], which you’ll meet in chapter 63. And compilers, Viscosity included, lean on lookup tables constantly: from each name to its declaration, from each value to what is known about it.
Your turn
Write countEach(xs). It returns a Map from each value in the array xs to how many times it appears. For example countEach(['a', 'b', 'a']) maps 'a' to 2 and 'b' to 1. The number 1 and the string '1' are different values and must be counted separately.
function countEach(xs) {
// your code here
}
const m = countEach(['a', 'b', 'a']);
console.log(m);Write firstRepeat(xs). Read xs from left to right, and return the first value that you have already seen. If no value repeats, return undefined. For [3, 1, 4, 1, 5, 3] the answer is 1: you reach the second 1 (index 3) before the second 3 (index 5).
function firstRepeat(xs) {
// your code here
}
const xs = [3, 1, 4, 1, 5, 3];
console.log(firstRepeat(xs));What you learned
- Object property names are always strings, so objects make poor lookup tables for non-string keys.
- A Map stores key → value entries, with keys of any type, compared
like
===. Useset,get,has,deleteandsize. - A Set stores unique values. Use
add,has,deleteandsize. for...ofvisits each value of an array, string, Map or Set, in order. Over a Map, each value is a[key, value]pair.- Hashing turns a key into a bucket number, so a lookup checks one bucket instead of everything.
That’s the end of Day 2. Next: functions that call themselves, and the stack that keeps track of every call.
Check your understanding
0/6- 1.
What does this print?
const m = new Map(); m.set('a', 1); m.set('a', 2); console.log(m.size, m.get('a')); - 2.
What does this print?
const m = new Map(); m.set({ id: 1 }, 'hi'); console.log(m.get({ id: 1 })); - 3.
What is
new Set([1, 2, 2, 3, 3, 3]).size? - 4.
Why is a Map a better lookup table than a plain object when the keys are particle objects?
- 5.
In
for (const e of m), wheremis a Map, what ise? - 6.
How does a hash table usually find a key without checking every entry?
References
- [1] MDN contributors. Map. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map (checked 2026-10-31)
- [2] MDN contributors. Set. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set (checked 2026-10-31)
- [3] MDN contributors. for...of. MDN Web Docs. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of (checked 2026-10-31)
- [4] Sathya Gunasekaran (2018). Optimizing hash tables: hiding the hash code. V8 blog (v8.dev), 29 January 2018. v8.dev/blog/hash-code (checked 2026-10-31)