Map and Set

So far you have two workhorses for grouping data: objects hold keyed collections, and arrays hold ordered ones. They cover a huge amount of ground, but they leak in specific ways. Object keys quietly become strings. Arrays make you scan the whole thing to check for duplicates. Map and Set are the built-ins that plug those two holes.

Object
keyed, string keys
Array
ordered, indexed
Map
keyed, any key type
Set
unique values, no keys
Four collection types and what each is good at

Map

A Map is a collection of key/value pairs, much like an object. The headline difference: a Map accepts a key of any type — numbers, booleans, objects, functions, even other maps. It never coerces the key to a string behind your back.

Here are the core methods and the one property you will reach for constantly:

  • new Map() — creates an empty map.
  • map.set(key, value) — stores value under key and returns the map.
  • map.get(key) — reads the value for key, or undefined if there’s no such key.
  • map.has(key) — returns true/false for whether the key exists.
  • map.delete(key) — removes the pair for key.
  • map.clear() — empties the whole map.
  • map.size — the current number of pairs (a property, not a method — no parentheses).

A quick tour that shows off the key-type behavior:

let map = new Map();

map.set('1', 'str1');   // a string key
map.set(1, 'num1');     // a numeric key
map.set(true, 'bool1'); // a boolean key

// a plain object would turn every key into a string.
// Map keeps the original type, so these two keys stay separate:
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'

alert( map.size ); // 3

Notice that 1 (the number) and '1' (the string) land in two different slots. In a plain object they’d collide, because obj[1] and obj['1'] are the same property.

‘1’‘str1’
1‘num1’
true‘bool1’
The number key 1 and the string key '1' are distinct entries in a Map

Watch it happen live. The two buttons store under the number 1 and the string '1' respectively — the map keeps them in separate slots, so its size climbs to 2.

interactiveNumber key vs string key stay distinct

Objects as keys

This is where Map earns its place. You can key a map by an object reference:

let maya = { name: "Maya" };

// for every user, store how many times they visited
let visitsCountMap = new Map();

// maya (the object) is the key
visitsCountMap.set(maya, 123);

alert( visitsCountMap.get(maya) ); // 123

Try the same trick with a plain object and it falls apart. An object can only hold string and symbol keys, so any object you use as a key gets run through toString() first — and for a generic object that produces the string "[object Object]". Every object key collapses to that one string:

let maya = { name: "Maya" };
let omar = { name: "Omar" };

let visitsCountObj = {}; // try a plain object as the store

visitsCountObj[omar] = 234;  // key becomes "[object Object]"
visitsCountObj[maya] = 123; // same key string — overwrites omar's entry

// only one value survived:
alert( visitsCountObj["[object Object]"] ); // 123

Both omar and maya stringify to the identical "[object Object]", so the second write clobbers the first. A Map sidesteps this entirely by comparing keys by identity, not by their string form.

plain object
omar&maya“[object Object]”
one slot, last write wins
Map
omar234
maya123
two distinct slots
Plain object collapses both object keys into one string; Map keeps them apart

Keying by object is genuinely useful: counting visits per user, caching a computed result per DOM node, attaching metadata to values you don’t own. An ordinary object can’t do any of it.

Here is that visit counter in action. Both users are { name: ... } objects, yet the Map counts them independently — something a plain object keyed by those same references could never do.

interactiveCounting visits with object keys

Iterating a Map

A map hands you three iterables, and every one of them walks entries in insertion order:

  • map.keys() — an iterable of the keys.
  • map.values() — an iterable of the values.
  • map.entries() — an iterable of [key, value] pairs. This is the default, so for..of over the map itself uses it.
let boltsMap = new Map([
  ['hex',      240],
  ['carriage', 180],
  ['anchor',   60]
]);

// iterate over keys (bolt types)
for (let boltType of boltsMap.keys()) {
  alert(boltType); // hex, carriage, anchor
}

// iterate over values (counts)
for (let count of boltsMap.values()) {
  alert(count); // 240, 180, 60
}

// iterate over [key, value] entries
for (let entry of boltsMap) { // same as boltsMap.entries()
  alert(entry); // hex,240  (then carriage,180 ...)
}
entries()[‘hex’, 240][‘carriage’, 180][‘anchor’, 60]
keys()‘hex’‘carriage’‘anchor’
values()24018060
The three iterators pull different slices from the same ordered entries

There’s also a built-in forEach, mirroring the array method:

// runs the callback for each (key, value) pair
boltsMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // hex: 240  (then ...)
});

Watch the argument order here: value comes first, then key. That’s the reverse of how you write set(key, value), and it trips people up constantly.

Object.entries: build a Map from an object

The Map constructor takes any iterable of [key, value] pairs and loads them in:

// array of [key, value] pairs
let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);

alert( map.get('1') ); // str1

So when you already have a plain object and want a map out of it, you just need something that turns the object into that pairs format. Object.entries(obj) does precisely that:

let obj = {
  name: "Maya",
  age: 27
};

let map = new Map(Object.entries(obj));

alert( map.get('name') ); // Maya

Object.entries(obj) returns [ ["name","Maya"], ["age", 27] ] — an array of pairs, the exact shape the Map constructor wants.

obj
name: ‘Maya’
age: 27
Object.entries →
[[k,v], …]
[‘name’, ‘Maya’]
[‘age’, 27]
new Map →
Map
‘name’ → ‘Maya’
‘age’ → 27
Object.entries reshapes an object into the pairs a Map consumes

Object.fromEntries: build an object from a Map

The reverse trip exists too. Object.fromEntries takes an iterable of [key, value] pairs and produces a plain object:

let prices = Object.fromEntries([
  ['pencil', 1],
  ['eraser', 2],
  ['stapler', 4]
]);

// prices = { pencil: 1, eraser: 2, stapler: 4 }

alert(prices.eraser); // 2

That’s handy when your data lives in a Map but you need to hand it to code — a library, a JSON serializer, an API — that expects a plain object:

let map = new Map();
map.set('pencil', 1);
map.set('eraser', 2);
map.set('stapler', 4);

let obj = Object.fromEntries(map.entries()); // build a plain object (*)

// obj = { pencil: 1, eraser: 2, stapler: 4 }

alert(obj.eraser); // 2

map.entries() yields pairs in exactly the format Object.fromEntries reads, so it just works.

Line (*) can lose the .entries() call:

let obj = Object.fromEntries(map); // omit .entries()

That’s equivalent, because Object.fromEntries accepts any iterable, and a map’s default iteration already produces the same [key, value] pairs as map.entries(). You get an object with the map’s keys and values.

Set

A Set is a collection of values with no keys, where each value can appear at most once. Think of it as a bag that automatically refuses duplicates.

The main methods:

  • new Set(iterable) — creates the set; if you pass an iterable (usually an array), its values get copied in.
  • set.add(value) — adds a value and returns the set.
  • set.delete(value) — removes a value; returns true if it was there, false if not.
  • set.has(value) — returns true/false for membership.
  • set.clear() — empties the set.
  • set.size — the number of values.

The defining trait: calling set.add(value) again with a value that’s already present does nothing. Duplicates simply don’t accumulate.

A classic use: track everyone who visited, without double-counting repeat visitors.

let set = new Set();

let maya = { name: "Maya" };
let raj = { name: "Raj" };
let lena = { name: "Lena" };

// visits — some people show up more than once
set.add(maya);
set.add(raj);
set.add(lena);
set.add(maya);
set.add(lena);

// the set kept only the unique visitors
alert( set.size ); // 3

for (let user of set) {
  alert(user.name); // Maya, then Raj, then Lena
}
add() ×5mayarajlenamayalena
Setmayarajlenasize: 3
Five add() calls, but the Set holds three unique values

You could get the same result with an array plus an arr.find check on every insert, but performance suffers: find scans the whole array each time, so adding n items costs on the order of comparisons. A Set is built for membership tests and stays fast as it grows.

Iterating a Set

Loop a set with for..of or forEach:

let set = new Set(["oranges", "apples", "bananas"]);

for (let value of set) alert(value);

// same, with forEach:
set.forEach((value, valueAgain, set) => {
  alert(value);
});

The forEach callback receives the value twicevalue and valueAgain are identical. That looks odd until you see why: Map’s forEach callback is (value, key, map), and a set reuses that three-argument shape with the value standing in for the key slot. The payoff is that code written against one collection often drops onto the other with minimal edits.

A set also exposes the same iterator methods as a map, again for interchangeability:

  • set.keys() — an iterable of the values.
  • set.values() — identical to set.keys(), present for Map compatibility.
  • set.entries() — an iterable of [value, value] pairs, present for Map compatibility.

Type any comma-separated list and watch the duplicates fall away, order intact:

interactiveDedupe a list with [...new Set(arr)]

Summary

Map is a collection of keyed values. Its interface:

  • new Map(iterable) — creates the map, optionally seeded from an iterable of [key, value] pairs.
  • map.set(key, value) — stores a value; returns the map (so calls chain).
  • map.get(key) — reads the value, or undefined if the key is missing.
  • map.has(key)true if the key exists, else false.
  • map.delete(key) — removes the pair; returns true if the key was present.
  • map.clear() — removes everything.
  • map.size — the current pair count.

What sets Map apart from a plain object:

  • Keys of any type, including objects, compared by identity (SameValueZero).
  • Guaranteed insertion order, a real size, and iteration helpers built in.

Set is a collection of unique values. Its interface:

  • new Set(iterable) — creates the set, optionally seeded from an iterable of values.
  • set.add(value) — adds a value (no-op if it’s already there); returns the set.
  • set.delete(value) — removes the value; returns true if it was present.
  • set.has(value)true if the value exists, else false.
  • set.clear() — removes everything.
  • set.size — the current value count.

Both Map and Set iterate in insertion order. So they aren’t unordered — but you can’t reorder their elements or index into them by position the way you can with an array.