Map Toolbox 0/8

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.

JavaScript ES202415 tools
01

Make and read

Create a Map, put values in with set, and read them back with get.

Start herenew MapsetgetMust know

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.
new Map(pairs?)

Makes an empty map, or one filled from a list of [key, value] pairs.

See the example
Gives a new mapMakes a new one
set(key, value)

Adds the pair, or replaces the value if the key is already there.

See the example
Gives the same mapChanges it
get(key)

Reads the value stored under that key.

See the example
Gives the value or undefinedOnly reads
has(key)

Checks if the key is in the map.

See the example
Gives true or falseOnly reads
size

Counts the entries. It is a property, so no brackets.

See the example
Gives a numberOnly reads
make.jsJAVASCRIPT
const 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.

Terminal OUTPUT
$ node make.js
45
undefined
true
3
Map(3) { 'tea' => 20, 'coffee' => 45, 'juice' => 60 }
start.jsJAVASCRIPT
// 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.

Terminal OUTPUT
$ node start.js
admin 2
number one | text one | a boolean
02

Map or object?

Know when a Map is the better choice than a plain object, and when it is not.

Choosingkey typesordersize
QuestionPlain objectMap
What can a key be?text or symbol onlyanything, even objects
Does it keep your order?mostly, but numbers jump firstalways, in the order added
How do I count it?Object.keys(obj).lengthmap.size
Can I loop it directly?no, use Object.entriesyes, with for...of
Hidden keys to trip on?yes, like toStringnone
Works with JSON?YesNo convert first
Best forfixed shapes like a user recordlookups that grow and shrink
versus.jsJAVASCRIPT
// 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.

Terminal OUTPUT
$ node versus.js
[ '1' ] text
2 number text
[ '[object Object]' ]
visited
03

Change and remove

Update a value, remove one entry, or empty the whole map.

Changingdeleteclear

Picture it: delete

cart"pen"5"book"1cart.delete("book")cart"pen"5The pair is gone and size drops by one.
set(sameKey, value)

Setting a key that exists replaces its value. Size stays the same.

See the example
Gives the same mapChanges it
delete(key)

Removes that key and its value.

See the example
Gives true if it was thereChanges it
clear()

Removes every entry at once.

See the example
Gives undefinedChanges it
change.jsJAVASCRIPT
const 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.

Terminal OUTPUT
$ node change.js
5 2
true
false
Map(1) { 'pen' => 5 }
0
04

Loop through

Walk through every pair in the order you added them, or pull out just the keys or values.

Loopingfor...ofkeysvaluesforEach

Picture it: spread a Map

scores"asha"91"ravi"78[...scores]["asha", 91]["ravi", 78]A Map spreads into [key, value] pairs.
for (const [k, v] of map)

Loops over every pair and unpacks each into two names.

See the example
Gives nothingOnly reads
keys()

Lists the keys. Spread it into an array with [...map.keys()].

See the example
Gives the keys, one by oneOnly reads
values()

Lists the values.

See the example
Gives the values, one by oneOnly reads
entries()

Lists the pairs. This is what for...of uses by default.

See the example
Gives pairs, one by oneOnly reads
forEach(value, key)

Runs your helper on every pair. Note the value comes first.

See the example
Gives nothingOnly reads
loop.jsJAVASCRIPT
const 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.

Terminal OUTPUT
$ node loop.js
asha: 91
ravi: 78
meera: 85
[ 'asha', 'ravi', 'meera' ]
[ 91, 78, 85 ]
asha passed with 91
meera passed with 85
05

Convert and save

Move data between Maps, objects, arrays and JSON without losing anything.

ConvertingObject.entriesfromEntriesJSON
FromToUse
ObjectMapnew Map(Object.entries(obj))
MapObjectObject.fromEntries(map)
MapArray of pairs[...map]
MapJSON textJSON.stringify([...map])
JSON textMapnew Map(JSON.parse(text))
convert.jsJAVASCRIPT
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.

Terminal OUTPUT
$ node convert.js
dark
{ theme: 'dark', size: 16 }
[ [ 'theme', 'dark' ], [ 'size', 16 ] ]
{}
[["theme","dark"],["size",16]]
16
06

Count and group

Count how often things appear, and sort a list into named buckets.

Real usescountingMap.groupBy

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.
get(k) ?? 0

Reads the current count, or 0 if the key is new.

See the example
Gives a numberOnly reads
Map.groupBy(list, fn)

Puts each item into a bucket named by what your helper returns.

See the example
Gives a new map of arraysMakes a new one
count.jsJAVASCRIPT
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.

Terminal OUTPUT
$ node count.js
3 2 undefined
[ [ 'the', 3 ], [ 'cat', 2 ], [ 'saw', 1 ] ]
group.jsJAVASCRIPT
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.

Terminal OUTPUT
$ node group.js
[ 'paid', 'pending' ]
[ 1, 3 ]
07

WeakMap

Attach extra data to objects without keeping those objects alive forever.

MemoryWeakMapobjects only

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 youMapWeakMap
Use text or numbers as keysYesNo
Use objects as keysYesYes
Check size or loop over itYesNo
Let unused keys be cleaned upNoYes
weak.jsJAVASCRIPT
// 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.

Terminal OUTPUT
$ node weak.js
10:42 true
TypeError: Invalid value used as weak map key
undefined undefined
08

All together

Build a small cache that keeps only the most recently used items, using the order a Map remembers.

Putting it togethersetdeletekeyssize

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.

cache.jsJAVASCRIPT
// 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.

Terminal OUTPUT
$ node cache.js
[ 'user:1', 'user:3' ]
undefined Meera
TerminalBASH
# 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 toUseYou get
Make a mapnew Map()an empty map
Start with datanew Map([[k, v], ...])a filled map
Add or updatemap.set(k, v)the same map
Read a valuemap.get(k)the value or undefined
Check a keymap.has(k)true or false
Count entriesmap.sizea number
Remove onemap.delete(k)true or false
Empty itmap.clear()an empty map
Loop over itfor (const [k, v] of map)every pair, in order
Get an array of keys[...map.keys()]an array
Turn it into an objectObject.fromEntries(map)a plain object
Save it as JSONJSON.stringify([...map])JSON text
Group a listMap.groupBy(list, fn)a map of arrays
Tag objects without leaksnew WeakMap()a weak map