Map
A Map is a built-in collection that stores key/value pairs where the keys can be any value — objects, functions, numbers, or strings — not just the string keys a plain object allows. It remembers the order in which entries were inserted, exposes a handy size property, and is directly iterable. When you need a dictionary-like structure with rich keys or frequent additions and removals, Map is almost always the better choice than an object.
Why use a Map instead of an object
Plain objects work fine as simple string-keyed dictionaries, but they carry baggage: keys are coerced to strings, they inherit properties from Object.prototype (so a key like toString can collide with built-ins), and there is no built-in size or clean iteration. Map was designed specifically for the “collection of keyed data” use case.
| Feature | Map | Plain object |
|---|---|---|
| Key types | Any value (objects, functions, NaN, etc.) | Strings and symbols only |
| Size | map.size | Object.keys(obj).length |
| Insertion order | Guaranteed | Mostly, but integer-like keys reorder |
| Iteration | Directly iterable (for...of) | Needs Object.keys/entries |
| Default keys | None — starts empty | Inherits from prototype |
| Performance on frequent add/delete | Optimized | Not optimized |
Reach for a
Mapwhenever keys are unknown ahead of time, are not strings, or the collection changes size often. Use a plain object for fixed, known-at-author-time string keys (like config records).
Creating a Map
Construct an empty Map, or seed it from an iterable of [key, value] pairs.
const empty = new Map();
const sizes = new Map([
["small", 8],
["medium", 12],
["large", 16],
]);
console.log(sizes.size); // 3
Output:
3
CRUD: set, get, has, delete
The core API is small and consistent. set() returns the map itself, so calls can be chained.
const cache = new Map();
// Create / update
cache.set("user:1", { name: "Ada" })
.set("user:2", { name: "Linus" });
// Read
console.log(cache.get("user:1")); // { name: 'Ada' }
console.log(cache.get("missing")); // undefined
// Existence check
console.log(cache.has("user:2")); // true
// Delete
cache.delete("user:1");
console.log(cache.has("user:1")); // false
// Remove everything
cache.clear();
console.log(cache.size); // 0
Output:
{ name: 'Ada' }
undefined
true
false
0
Because keys are compared by reference (using the SameValueZero algorithm), object keys are distinct unless they are literally the same reference:
const a = {};
const b = {};
const m = new Map();
m.set(a, "first").set(b, "second");
console.log(m.size); // 2 — a and b are different objects
console.log(m.get(a)); // 'first'
console.log(m.get({})); // undefined — a brand-new object
Output:
2
first
undefined
Iterating a Map
A Map is iterable, so for...of yields [key, value] pairs in insertion order. You can also use keys(), values(), entries(), or forEach().
const scores = new Map([
["Ada", 95],
["Linus", 88],
["Grace", 99],
]);
for (const [name, score] of scores) {
console.log(`${name}: ${score}`);
}
// Just the keys, or just the values
console.log([...scores.keys()]); // ['Ada', 'Linus', 'Grace']
console.log([...scores.values()]); // [95, 88, 99]
// forEach gives (value, key) — note the order!
scores.forEach((score, name) => console.log(`${name} scored ${score}`));
Output:
Ada: 95
Linus: 88
Grace: 99
[ 'Ada', 'Linus', 'Grace' ]
[ 95, 88, 99 ]
Ada scored 95
Linus scored 88
Grace scored 99
Watch the argument order:
Map.prototype.forEach((value, key) => ...)passes the value first, mirroringArray.prototype.forEach.
Converting to and from arrays and objects
Spreading a Map (or calling Array.from) produces an array of pairs, which makes sorting, filtering, and mapping trivial.
const prices = new Map([
["apple", 3],
["banana", 1],
["cherry", 5],
]);
// Map -> array of entries
const entries = [...prices]; // [['apple', 3], ['banana', 1], ['cherry', 5]]
// Sort by price, then rebuild a Map
const sorted = new Map([...prices].sort((a, b) => a[1] - b[1]));
console.log([...sorted.keys()]); // ['banana', 'apple', 'cherry']
// Map -> plain object
const obj = Object.fromEntries(prices);
console.log(obj); // { apple: 3, banana: 1, cherry: 5 }
// Object -> Map
const back = new Map(Object.entries(obj));
console.log(back.get("banana")); // 1
Output:
[ 'banana', 'apple', 'cherry' ]
{ apple: 3, banana: 1, cherry: 5 }
1
Note that Object.fromEntries only keeps keys that are valid object keys — string or symbol keys survive, but object keys are coerced to strings, so convert to a plain object only when your keys are strings.
Best Practices
- Prefer
Mapover a plain object whenever keys are dynamic, non-string, or the collection is frequently mutated. - Chain
set()calls for concise initialization, since each call returns the map. - Use
map.sizeinstead of computing length from keys, andmap.has(key)instead of comparingget(key)toundefined(a storedundefinedvalue is indistinguishable otherwise). - Remember that object keys are matched by reference — keep a handle to the key object if you need to look it up later.
- Seed a
MapfromObject.entries(obj)and export it back withObject.fromEntries(map)only when keys are strings. - Use the spread operator to leverage array methods (
sort,filter,map) and then reconstruct theMap.