ES6 gave JavaScript two proper collection types: Map and Set.
Before they arrived, we reached for plain objects as dictionaries and arrays as lists. That works most of the time — until you need keys that aren't strings, values that can't repeat, or frequent inserts and deletes. Then it starts to feel like fighting the language.
Map and Set aren't just different syntax. They solve different problems: one manages a mapping from keys to values, the other manages a set of unique values.
- What Map and Set Are
- Creating and Basic Operations
- Keys, Values, and Uniqueness
- Order and Iteration
- Performance and size
- Map or Object
- Set or Array
- WeakMap and WeakSet
- Quick Reference
- When to Use Each
What Map and Set Are
A Map is a collection of key–value pairs. Each entry maps one key to one value, and keys can't repeat.
A Set is a collection of values. It stores values, not mappings, and values can't repeat.
One line to tell them apart: Map cares about what a key maps to; Set cares about whether a value is present.
// Map: map a key to a value
const scores = new Map();
scores.set('Charmy', 95);
scores.get('Charmy'); // 95
// Set: only the presence of a value matters
const tags = new Set();
tags.add('JavaScript');
tags.has('JavaScript'); // trueBoth are iterable, both remember insertion order, and both expose size for the number of elements. What differs is what they hold.
Creating and Basic Operations
Map
The core Map methods are set, get, has, and delete:
const map = new Map();
map.set('name', 'Charmy');
map.set('age', 28);
map.get('name'); // 'Charmy'
map.has('age'); // true
map.size; // 2
map.delete('age'); // true
map.size; // 1
map.clear(); // empties the mapset returns the map itself, so calls can be chained:
const map = new Map()
.set('a', 1)
.set('b', 2)
.set('c', 3);You can also initialize a Map directly from an array of key–value pairs:
const map = new Map([
['name', 'Charmy'],
['age', 28],
]);Set
The core Set methods are add, has, and delete:
const set = new Set();
set.add('apple');
set.add('banana');
set.add('apple'); // duplicate, ignored
set.has('apple'); // true
set.size; // 2
set.delete('banana'); // true
set.clear(); // empties the setadd also returns the set itself, so it chains too. The most common pattern is to pass an array straight into the constructor:
const set = new Set([1, 2, 2, 3, 3, 3]);
set.size; // 3 — duplicates are dropped automaticallyKeys, Values, and Uniqueness
This is where Map and Set differ most from plain objects and arrays.
Any type can be a key or value
A Map key can be any type: an object, a function, NaN, even undefined. An Object key, by contrast, can only be a string or a symbol — even numbers get quietly coerced to strings.
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, 'objects can be keys');
map.set(NaN, 'so can NaN');
map.set(true, 'and booleans');
map.get(objKey); // 'objects can be keys'
map.get(NaN); // 'so can NaN'Set is the same — it accepts values of any type.
Object keys compare by reference
This is the easiest trap to fall into. Objects compare by reference, not by content:
const map = new Map();
map.set({ id: 1 }, 'A');
map.get({ id: 1 }); // undefined — these are two different objectsThe two { id: 1 } literals look identical, but they're distinct objects with different references, so the lookup misses. To retrieve the value, you have to hold on to the same reference:
const key = { id: 1 };
const map = new Map();
map.set(key, 'A');
map.get(key); // 'A'How uniqueness is decided: SameValueZero
To decide whether two keys are the same (in Map) or a value is a duplicate (in Set), both use an algorithm called SameValueZero. It behaves just like ===, with two exceptions:
NaNis considered equal toNaN+0and-0are treated as the same value
const set = new Set([NaN, NaN, 0, -0]);
set.size; // 2 — the two NaNs collapse, and +0/-0 collapseThat NaN handling is genuinely useful: dedupe with a Set and NaN is handled correctly, whereas [NaN].indexOf(NaN) returns -1 even though [NaN].includes(NaN) is true.
Objects still compare by reference:
const a = { id: 1 };
const set = new Set([a, a, { id: 1 }]);
set.size; // 2 — the first two share a reference, the third is a new objectOrder and Iteration
Both Map and Set remember insertion order, and they iterate in that order. This is unlike plain objects, where integer-like keys get reordered by the engine.
Iterating a Map
Iterating a Map with for...of yields a [key, value] pair each round:
const map = new Map([
['name', 'Charmy'],
['age', 28],
]);
for (const [key, value] of map) {
console.log(key, value);
}
// 'name' 'Charmy'
// 'age' 28Map also provides three iterator methods:
map.keys(); // 'name', 'age'
map.values(); // 'Charmy', 28
map.entries(); // ['name', 'Charmy'], ['age', 28]
map.forEach((value, key) => {
console.log(key, value);
});Iterating a Set
Iterating a Set yields values directly:
const set = new Set(['a', 'b', 'c']);
for (const value of set) {
console.log(value); // 'a' 'b' 'c'
}
set.forEach((value) => {
console.log(value);
});To mirror the Map interface, Set also keeps keys(), values(), and entries() — where entries() returns [value, value] pairs.
Converting to and from arrays
Both Map and Set convert to arrays with the spread operator or Array.from, and arrays convert back:
// Deduplicating an array — the classic use case
const unique = [...new Set([1, 1, 2, 3, 3])]; // [1, 2, 3]
// Converting between Map and object
const map = new Map([['a', 1], ['b', 2]]);
Object.fromEntries(map); // { a: 1, b: 2 }
new Map(Object.entries({ a: 1 })); // Map { 'a' => 1 }Performance and size
size is a property, not a method
To get the element count, both Map and Set read size directly:
map.size; // direct read, O(1)
set.size;A plain object, by contrast, makes you compute Object.keys(obj).length, which builds an array first — an O(n) operation.
Lookup speed
Map.has, Map.get, and Set.has are required by the spec to be, on average, sublinear — roughly O(1) — so they don't slow down as the collection grows. Array includes and indexOf are O(n): they scan from the front.
// The gap shows up when you check membership repeatedly over a large set
const list = new Set(bigArray);
items.forEach((item) => {
if (list.has(item)) { // ~O(1) on average
// ...
}
});Swap in bigArray.includes(item) and every check rescans the whole array, degrading the loop to O(n × m). Whenever you need frequent membership checks, Set is almost always the better choice.
Map or Object
Both can model "keys to values," but the trade-offs differ.
Reach for Map when:
- Keys aren't strings (objects, numbers, and functions all need to work as keys)
- You add and remove entries frequently
- You need reliable insertion order and a cheap
size - You don't want inherited prototype properties getting in the way
Objects carry one easy-to-forget gotcha: they come with a prototype chain by default, so some keys appear to exist:
const obj = {};
obj['toString']; // a function, inherited from the prototype — not something you set
'toString' in obj; // true
const map = new Map();
map.has('toString'); // false — much cleanerNumeric keys also get reordered in an object, whereas Map preserves insertion order faithfully:
const obj = {};
obj[2] = 'two';
obj[1] = 'one';
Object.keys(obj); // ['1', '2'] — numeric keys are sorted
const map = new Map();
map.set(2, 'two');
map.set(1, 'one');
[...map.keys()]; // [2, 1] — insertion order preservedReach for Object when:
- You need
JSON.stringify(aMapdoesn't serialize directly) - The shape is fixed and you're using it as a record with fields
- You want literal syntax
{ }and destructuring
Set or Array
Both hold a list of values; the difference is duplicates and lookup.
Reach for Set when:
- Values must be unique (deduplication)
- You frequently check whether a value is present
const arr = [1, 2, 3];
arr.includes(2); // O(n)
const set = new Set([1, 2, 3]);
set.has(2); // ~O(1) on averageReach for Array when:
- You need order and indexed access (
arr[0]) - Duplicate values are allowed
- You want the rich
map,filter, andreducemethods - You need
JSONserialization
A common pattern in practice is to dedupe with a Set and then convert back to an array to process:
const unique = [...new Set(rawList)].sort();WeakMap and WeakSet
Map and Set each have a "weak" counterpart: WeakMap and WeakSet. They exist for one reason — don't keep an object alive just because you're holding a reference to it.
What sets them apart:
WeakMapkeys andWeakSetvalues must be objects (no primitives)- They hold weak references — when an object has no other references, it can be garbage collected
- They aren't iterable, have no
size, and can't be cleared (since contents may vanish at any time, enumeration wouldn't be meaningful)
The classic use case is attaching extra data to an object without causing a memory leak — for example, a cache:
const cache = new WeakMap();
function process(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = heavyComputation(obj);
cache.set(obj, result);
return result;
}
// Once obj is no longer referenced elsewhere, its cache entry is collected automaticallyWith a regular Map, obj would stay alive purely because the Map holds it as a key — a leak.
Quick Reference
| Map | Set | |
|---|---|---|
| Stores | Key–value pairs | Single values |
| Uniqueness | Keys are unique | Values are unique |
| Add | set(key, value) | add(value) |
| Read | get(key) | check with has |
| Size | size | size |
| Order | Insertion order | Insertion order |
| Iteration yields | [key, value] | value |
| Key/value types | Any type | Any type |
| Weak variant | WeakMap | WeakSet |
| Typical use | Lookups, associating data | Dedup, membership checks |
When to Use Each
Use Map
When keys aren't strings, or keys are objects:
const elementState = new Map();
elementState.set(domNode, { active: true });When you add and remove often and care about order and size:
const cart = new Map();
cart.set(productId, quantity);
cart.delete(productId);
cart.size;Use Set
Deduplicating an array:
const unique = [...new Set([1, 1, 2, 3])]; // [1, 2, 3]Fast membership checks:
const blocked = new Set(['spam', 'ads']);
if (blocked.has(category)) {
// block it
}Conclusion
Map and Set fill the gaps that objects and arrays leave behind.
Need to map keys to values, with keys that aren't necessarily strings? Use a Map. Need a collection of unique values you'll check membership against often? Use a Set.
Objects and arrays are still great — especially when you need serialization, a fixed shape, or indexed access. The point isn't which one replaces the other; it's matching the shape of your data to the right tool.