JavaScript basicsMaps in plain wordsOne tool at a time
Map
Toolbox
A Map pairs each key with a value, like a phone book pairs names with numbers. This guide shows how to fill one, read it, loop through it, convert it and use it for counting and caching, with a small example for each that you can run.
Key terms in plain words
Never written code before? Start here. Every word below turns up later on this page, each with an everyday comparison drawn from a phone book, where you look up a name and get a number. Underlined words in the modules link back to these cards.
22 terms in 4 groups. Hover an underlined word anywhere on the page for a quick reminder, or click it to jump here.
What a Map is
A Map works like a phone book. Each line has a name to look up and the number written beside it.
- Map
- Think of it as a phone book you can add to and cross out of
- A collection of pairs, where you store a value under a key and look it up again later by that key. It remembers the order you added things and always knows how many it holds.
- For example
new Map([["tea", 20], ["coffee", 45]])is a Map with two pairs. - Key
- Think of it as the name you look up in the phone book
- The label a value is stored under. Each key appears only once in a Map. Unlike a plain object, a Map lets the key be anything, even a number or a whole object.
- For example In
prices.set("tea", 20)the key is "tea". - Value
- Think of it as the phone number printed next to the name
- The thing stored under a key. It can be a number, some text, a list or anything else, and it can repeat across keys.
- For example In
prices.set("tea", 20)the value is 20. - Entry (pair)
- Think of it as one full line in the phone book, name and number together
- One key with its value. When you list or loop over a Map you get entries, each written as a tiny two-item array
[key, value]. - For example
["tea", 20]is one entry. - Object
- Think of it as a printed form with fixed boxes to fill in
- JavaScript's everyday way to group named values. It can do a Map's job, but it turns every key into text and has no size of its own, so it suits records whose shape you know in advance.
- For example
{ theme: "dark", size: 16 }is a plain object. - String
- Think of it as a name written in letters
- A piece of text, written between quotes. An object quietly turns every key into one of these, which is why
1and"1"clash there but not in a Map.
Reading and changing it
Looking a name up, writing a new line in, and crossing one out.
- Property
- Think of it as a fact printed on the cover, such as the number of pages
- A value you read from something without calling it, so there are no brackets. A Map's
sizeis a property: it is just there to read. - For example
prices.size, notprices.size(). - undefined
- Think of it as looking up a name that is not in the book
- JavaScript's word for "no value here".
getgives it back when the key is missing, rather than raising an error. - For example
prices.get("soda")givesundefined. - Chaining
- Think of it as writing three new lines in one sitting without closing the book
- Because
sethands the same Map back, you can call the nextsetstraight after it on the same line. - For example
prices.set("coffee", 45).set("juice", 60) - Fallback
- Think of it as writing 0 when a name has no number yet
- A value to use when the real one is missing.
??means "use the left side, unless it is empty, then use the right". It is how counting starts from zero. - For example
(counts.get(word) ?? 0) + 1
Walking through it
Reading the book from the first line to the last, or copying it out in another shape.
- Insertion order
- Think of it as a phone book that lists names in the order they were written in, not A to Z
- A Map gives its entries back in the order you added them. The first key is always the oldest, which the cache in module 08 relies on.
- Loop (for...of)
- Think of it as running your finger down the page, one line at a time
- Repeating the same step for every entry.
for...ofhands you each pair in turn and can unpack it into two names, the key and the value. - For example
for (const [name, score] of scores) { ... } - Callback
- Think of it as a note of instructions you leave for whoever reads each line
- A function you pass to a method so it can run it for you, once per entry. This page calls it the helper. Map's
forEachhands it the value first, then the key. - For example
scores.forEach((score, name) => ...) - Iterator
- Think of it as a reader who calls out one line each time you ask, but never hands you the page
- What
keys,valuesandentriesgive back. It produces items one at a time and is not an array, so it has nomaporfilteruntil you spread it into one. - For example
[...scores.keys()]turns the iterator into an array. - Spread syntax
- Think of it as copying every line of the book out onto a fresh sheet
- Three dots,
[...something], which lays its items out one by one inside a new array. It is how you turn a Map or an iterator into a normal array. - For example
[...prices]gives an array of pairs. - Array
- Think of it as a numbered list on a single sheet
- An ordered list of values, written in square brackets. Arrays have tools like
map,filterandsortthat a Map does not, so you often convert to one first. - For example
["asha", "ravi"]is an array. - JSON
- Think of it as a typed-up copy of the book you can post or save to a file
- A plain text format for saving or sending data. It understands lists and objects but not Maps, so a Map turned straight into JSON comes out as an empty
{}. Convert it to pairs first. - For example
JSON.stringify([...map])keeps the data.
Putting it to work
Counting, sorting into groups, remembering recent answers and letting go of old ones.
- Grouping
- Think of it as splitting the phone book into separate books for each town
- Sorting a list into named buckets, so every item with the same label ends up together.
Map.groupBydoes it in one call, and the bucket names can be any type. - For example Group orders by status: one bucket for "paid", one for "pending".
- Cache
- Think of it as a sticky note of numbers you rang recently, so you skip the book next time
- A small store of answers you already worked out or fetched. It saves doing the same slow job twice, and it needs a limit so it does not grow forever.
- Least recently used
- Think of it as tearing out the line nobody has looked at for the longest time
- A rule for which item a full cache throws away: the one nobody asked for in the longest time. A Map makes it easy, because reading a key and setting it again moves it to the end.
- WeakMap
- Think of it as notes pencilled beside a name that fade once the person moves away
- A Map that only takes objects as keys and holds them loosely. Once nothing else in your program uses that object, its entry can vanish on its own. It has no size and cannot be looped over.
- Garbage collection
- Think of it as the cleaner who clears out books nobody can reach any more
- JavaScript frees memory on its own by throwing away values your program can no longer reach. A normal Map keeps its keys reachable, so they are never cleared; a WeakMap does not.
Make and read
Create a Map, put values in with set, and read them back with get.
A Map is a list of key and value pairs, like a phone book: look up a name, get a number. It does the same job as a plain object, but it is built for it. Keys can be any type, it always knows its size, and it remembers the order you added things.
Picture it: one Map
prices"tea"20"coffee"45"juice"60prices.get("coffee")45Ask with a key, get its value.Makes an empty map, or one filled from a list of [key, value] pairs.
See the exampleAdds the pair, or replaces the value if the key is already there.
See the exampleReads the value stored under that key.
See the exampleChecks if the key is in the map.
See the exampleconst prices = new Map();
prices.set("tea", 20); // add a key and its value
prices.set("coffee", 45).set("juice", 60); // set gives the map back, so you can chain
console.log(prices.get("coffee")); // read a value
console.log(prices.get("soda")); // a missing key gives undefined
console.log(prices.has("tea")); // check a key exists
console.log(prices.size); // how many entries
console.log(prices);
Easy way to remember: set puts in, get takes out. Because set returns the map, you can chain several sets on one line.
$ node make.js 45 undefined true 3 Map(3) { 'tea' => 20, 'coffee' => 45, 'juice' => 60 }
// start with a list of [key, value] pairs
const roles = new Map([
["asha", "admin"],
["ravi", "editor"],
]);
console.log(roles.get("asha"), roles.size);
// keys can be any type, not just text
const labels = new Map([
[1, "number one"],
["1", "text one"],
[true, "a boolean"],
]);
console.log(labels.get(1), "|", labels.get("1"), "|", labels.get(true));
Why it matters: the number 1 and the text "1" are two different keys in a Map. In a plain object they would collide.
$ node start.js admin 2 number one | text one | a boolean
Map or object?
Know when a Map is the better choice than a plain object, and when it is not.
| Question | Plain object | Map |
|---|---|---|
| What can a key be? | text or symbol only | anything, even objects |
| Does it keep your order? | mostly, but numbers jump first | always, in the order added |
| How do I count it? | Object.keys(obj).length | map.size |
| Can I loop it directly? | no, use Object.entries | yes, with for...of |
| Hidden keys to trip on? | yes, like toString | none |
| Works with JSON? | Yes | No convert first |
| Best for | fixed shapes like a user record | lookups that grow and shrink |
// an object turns every key into text
const obj = {};
obj[1] = "number";
obj["1"] = "text"; // overwrites, because 1 became "1"
console.log(Object.keys(obj), obj[1]);
// a map keeps 1 and "1" apart
const map = new Map();
map.set(1, "number").set("1", "text");
console.log(map.size, map.get(1), map.get("1"));
// objects as keys: the object version breaks, the map works
const user = { id: 7 };
const seen = {};
seen[user] = "visited";
console.log(Object.keys(seen)); // the key became "[object Object]"
const visits = new Map([[user, "visited"]]);
console.log(visits.get(user));
Rule of thumb: use an object when you know the keys ahead of time. Use a Map when keys come from data, change often, or are not text.
$ node versus.js [ '1' ] text 2 number text [ '[object Object]' ] visited
Change and remove
Update a value, remove one entry, or empty the whole map.
Picture it: delete
cart"pen"5"book"1cart.delete("book")cart"pen"5The pair is gone and size drops by one.Setting a key that exists replaces its value. Size stays the same.
See the exampleRemoves that key and its value.
See the exampleconst cart = new Map([["pen", 2], ["book", 1]]);
cart.set("pen", 5); // same key again: the value is replaced
console.log(cart.get("pen"), cart.size);
console.log(cart.delete("book")); // true: it was there and is now gone
console.log(cart.delete("bag")); // false: nothing to remove
console.log(cart);
cart.clear(); // empty the whole map
console.log(cart.size);
Easy way to remember: delete tells you whether it found something, so you can use its answer in an if.
$ node change.js 5 2 true false Map(1) { 'pen' => 5 } 0
Loop through
Walk through every pair in the order you added them, or pull out just the keys or values.
Loops over every pair and unpacks each into two names.
See the exampleLists the keys. Spread it into an array with [...map.keys()].
See the exampleLists the pairs. This is what for...of uses by default.
See the exampleRuns your helper on every pair. Note the value comes first.
See the exampleconst scores = new Map([["asha", 91], ["ravi", 78], ["meera", 85]]);
// for...of gives [key, value] pairs, in the order you added them
for (const [name, score] of scores) {
console.log(`${name}: ${score}`);
}
console.log([...scores.keys()]); // just the keys
console.log([...scores.values()]); // just the values
// forEach hands you the value first, then the key
scores.forEach((score, name) => {
if (score > 80) console.log(name, "passed with", score);
});
Watch for this: keys, values and entries give you an iterator, not an array. Wrap them in [...] when you want array tools like map or filter.
$ node loop.js asha: 91 ravi: 78 meera: 85 [ 'asha', 'ravi', 'meera' ] [ 91, 78, 85 ] asha passed with 91 meera passed with 85
Convert and save
Move data between Maps, objects, arrays and JSON without losing anything.
| From | To | Use |
|---|---|---|
| Object | Map | new Map(Object.entries(obj)) |
| Map | Object | Object.fromEntries(map) |
| Map | Array of pairs | [...map] |
| Map | JSON text | JSON.stringify([...map]) |
| JSON text | Map | new Map(JSON.parse(text)) |
const settings = { theme: "dark", size: 16 };
const asMap = new Map(Object.entries(settings)); // object to map
console.log(asMap.get("theme"));
const backToObject = Object.fromEntries(asMap); // map to object
console.log(backToObject);
console.log([...asMap]); // map to array of pairs
// JSON does not understand maps
console.log(JSON.stringify(asMap)); // {} : the data is lost
console.log(JSON.stringify([...asMap])); // save it as pairs instead
const restored = new Map(JSON.parse('[["theme","dark"],["size",16]]'));
console.log(restored.get("size"));
Why it matters: JSON.stringify(map) quietly gives {}. Always turn the Map into pairs before you save or send it.
$ node convert.js dark { theme: 'dark', size: 16 } [ [ 'theme', 'dark' ], [ 'size', 16 ] ] {} [["theme","dark"],["size",16]] 16
Count and group
Count how often things appear, and sort a list into named buckets.
Picture it: counting
thecatthedogthecounts.set(w, (counts.get(w) ?? 0) + 1)counts"the"3"cat"1"dog"1New words start at 0, then go up by one.const words = "the cat saw the dog and the cat ran".split(" ");
const counts = new Map();
for (const word of words) {
counts.set(word, (counts.get(word) ?? 0) + 1); // start at 0 if new
}
console.log(counts.get("the"), counts.get("cat"), counts.get("bird"));
// sort by count, biggest first, and keep the top three
const top = [...counts].sort((a, b) => b[1] - a[1]).slice(0, 3);
console.log(top);
The pattern: read the old value with a fallback, add to it, and set it back. It works for totals, counts and running sums.
$ node count.js 3 2 undefined [ [ 'the', 3 ], [ 'cat', 2 ], [ 'saw', 1 ] ]
const orders = [
{ id: 1, status: "paid" },
{ id: 2, status: "pending" },
{ id: 3, status: "paid" },
];
// Map.groupBy puts each item in a bucket named by what your helper returns
const byStatus = Map.groupBy(orders, (o) => o.status);
console.log([...byStatus.keys()]);
console.log(byStatus.get("paid").map((o) => o.id));
Needs: Node 21 or newer, or any current browser. Unlike Object.groupBy, the bucket names can be any type.
$ node group.js [ 'paid', 'pending' ] [ 1, 3 ]
WeakMap
Attach extra data to objects without keeping those objects alive forever.
A normal Map holds on to its keys, so an object used as a key never gets cleaned up while it sits in the map. A WeakMap holds its keys loosely. Once nothing else in your program uses that object, the entry can disappear by itself.
| Can you | Map | WeakMap |
|---|---|---|
| Use text or numbers as keys | Yes | No |
| Use objects as keys | Yes | Yes |
| Check size or loop over it | Yes | No |
| Let unused keys be cleaned up | No | Yes |
// a WeakMap only takes objects as keys, and forgets them
// when nothing else in your program is using that object
const lastSeen = new WeakMap();
let session = { user: "asha" };
lastSeen.set(session, "10:42");
console.log(lastSeen.get(session), lastSeen.has(session));
try {
lastSeen.set("asha", "10:42"); // text is not allowed as a key
} catch (err) {
console.log(err.constructor.name + ":", err.message);
}
session = null; // the entry can now be cleaned up by the garbage collector
console.log(typeof lastSeen.size, typeof lastSeen.keys); // no size, no looping
When to reach for it: caching results per object, or storing private details about DOM elements or request objects that you do not own.
$ node weak.js 10:42 true TypeError: Invalid value used as weak map key undefined undefined
All together
Build a small cache that keeps only the most recently used items, using the order a Map remembers.
A cache stores answers so you do not have to fetch them twice. It cannot grow forever, so when it is full it throws away the item nobody has asked for in the longest time. A Map makes this easy, because its first key is always the oldest one.
// a small "least recently used" cache that keeps only the newest items
class LruCache {
constructor(limit) {
this.limit = limit;
this.items = new Map(); // a map remembers insertion order
}
get(key) {
if (!this.items.has(key)) return undefined;
const value = this.items.get(key);
this.items.delete(key); // move it to the end: most recent
this.items.set(key, value);
return value;
}
set(key, value) {
this.items.delete(key);
this.items.set(key, value);
if (this.items.size > this.limit) {
const oldest = this.items.keys().next().value; // the first key is the oldest
this.items.delete(oldest);
}
}
}
const cache = new LruCache(2);
cache.set("user:1", "Asha");
cache.set("user:2", "Ravi");
cache.get("user:1"); // user:1 is now the most recent
cache.set("user:3", "Meera"); // full, so the oldest (user:2) goes
console.log([...cache.items.keys()]);
console.log(cache.get("user:2"), cache.get("user:3"));
What each part uses: has and get from module 01, delete and set from 03 to move a key to the end, size to know when it is full, and keys from 04 to find the oldest.
$ node cache.js [ 'user:1', 'user:3' ] undefined Meera
# save any example as a file and run it with Node 22 $ node cache.js # or paste it into your browser console with F12
Which one do I need?
Start from what you want to do, then read across to the tool and what it gives back.
| I want to | Use | You get |
|---|---|---|
| Make a map | new Map() | an empty map |
| Start with data | new Map([[k, v], ...]) | a filled map |
| Add or update | map.set(k, v) | the same map |
| Read a value | map.get(k) | the value or undefined |
| Check a key | map.has(k) | true or false |
| Count entries | map.size | a number |
| Remove one | map.delete(k) | true or false |
| Empty it | map.clear() | an empty map |
| Loop over it | for (const [k, v] of map) | every pair, in order |
| Get an array of keys | [...map.keys()] | an array |
| Turn it into an object | Object.fromEntries(map) | a plain object |
| Save it as JSON | JSON.stringify([...map]) | JSON text |
| Group a list | Map.groupBy(list, fn) | a map of arrays |
| Tag objects without leaks | new WeakMap() | a weak map |