Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 8 – Map, Set and weak collections

Map: keys of any type and the ES2026 upsert methods

How a JavaScript Map stores keys of any type in insertion order, when to choose it over a plain object, and how the ES2026 getOrInsert methods work.

  • Intermediate
  • 20 minutes
  • Examples run with Node.js 26.10.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Use a Map for lookups whose keys come from data, are not strings or change often
  • Explain how a Map compares keys, including NaN, -0, objects and arrays
  • Iterate a Map in insertion order and convert it to and from objects, entries and JSON
  • Count, group and cache with getOrInsert and getOrInsertComputed, with a fallback for older engines

Before you start

What you’ll learn · Before you start

What you will learn

  • Use a Map for lookups whose keys come from data, are not strings or change often
  • Explain how a Map compares keys, including NaN, -0, objects and arrays
  • Iterate a Map in insertion order and convert it to and from objects, entries and JSON
  • Count, group and cache with getOrInsert and getOrInsertComputed, with a fallback for older engines

Before you start

On this page

A Map is JavaScript’s built-in dictionary: it stores pairs of a key and a value, and the key can be any value at all, a number, an object or even NaN, not only a string. It remembers the order in which its keys were added, reports how many entries it holds in size, and has a method for each job: get, set, has, delete and clear. Choose it over a plain object when the keys come from data rather than from your code, when they are not strings, or when entries come and go all the time. ES2026 adds two methods for the chore every Map user repeats, “give me the value for this key, and store a default first if it is missing”: getOrInsert takes the default as a value, and getOrInsertComputed takes a function that runs only when the key is missing. This lesson runs each of them and gives their exact rules, including the cases the method names do not tell you.

The everyday methods

new Map() takes nothing, or any iterable of [key, value] pairs, such as an array of two-item arrays:

Post offices by PIN code JavaScript · map-basics.mjs
// PIN codes are numbers, so they make good Map keys as they are.
const offices = new Map([
  [411001, 'Pune GPO'],
  [110001, 'New Delhi GPO'],
]);
offices.set(560001, 'Bengaluru GPO').set(600001, 'Chennai GPO');

console.log('get 110001:', offices.get(110001));
console.log('get 400001:', offices.get(400001));
console.log('has 560001:', offices.has(560001), ' has "560001":', offices.has('560001'));
console.log('size:', offices.size);
console.log('delete 600001:', offices.delete(600001), ' again:', offices.delete(600001));
offices.clear();
console.log('size after clear:', offices.size);

Output

get 110001: New Delhi GPO
get 400001: undefined
has 560001: true  has "560001": false
size: 4
delete 600001: true  again: false
size after clear: 0

Recorded with Node.js 26.10.0 on macOS 26 arm64. To run it yourself: mise exec node@26.10.0 -- node map-basics.mjs

  • set(key, value) adds the key or replaces its value, and returns the Map itself, which is why two set calls can be chained on one line.
  • get(key) returns the value, or undefined when the key is missing. It cannot tell a missing key from a key whose value is undefined; has(key) can.
  • has(key) compares keys exactly, so the number 560001 and the string '560001' are different keys. A PIN code read from a form field is a string, and looking it up in a Map of numbers fails until you convert it.
  • size is a property you read, not a method you call. delete(key) returns true if it removed an entry and false if there was nothing to remove, and clear() removes every entry.

Keys of any type

A plain object can only have strings and symbols as property names, so every other key is converted to a string on the way in. A Map keeps each key exactly as it was given:

The same keys in an object and in a Map JavaScript · key-types.mjs
// The same four keys, stored in a plain object and in a Map.
const asObject = {};
const asMap = new Map();
for (const key of [1, '1', true, 'true']) {
  asObject[key] = typeof key;
  asMap.set(key, typeof key);
}
console.log('object keys:', Object.keys(asObject));
console.log('map keys:', [...asMap.keys()], 'size', asMap.size);

// Objects and arrays are keys by identity: the same object, not an equal-looking one.
const seat = [3, 14];
const booked = new Map([[seat, 'Asha']]);
console.log('same array:', booked.get(seat), ' new [3, 14]:', booked.get([3, 14]));

// NaN finds itself, and -0 is stored as 0 (SameValueZero).
const readings = new Map([[NaN, 'sensor offline']]);
readings.set(-0, 'zero');
console.log('get NaN:', readings.get(NaN), ' get 0:', readings.get(0));
console.log('stored key is -0:', Object.is([...readings.keys()][1], -0));

Output

object keys: [ '1', 'true' ]
map keys: [ 1, '1', true, 'true' ] size 4
same array: Asha  new [3, 14]: undefined
get NaN: sensor offline  get 0: zero
stored key is -0: false

Recorded with Node.js 26.10.0 on macOS 26 arm64. To run it yourself: mise exec node@26.10.0 -- node key-types.mjs

The object ends up with two properties, because 1 and '1' both became '1', and true and 'true' both became 'true'; the Map keeps all four keys, each with its own type. Three more rules decide which keys count as the same, and all three come from SameValueZero, the comparison the standard prescribes for Map keys:

  • Objects and arrays are keys by identity. The Map finds the seat array it was given, but a new [3, 14] is a different array, however alike it looks. To look things up by content, build a key from the content, such as the string '3-14', with a separator that cannot occur inside the parts.
  • NaN finds itself, although NaN === NaN is false. A sensor that reports NaN can be a key like any other.
  • -0 is stored as 0. Before a key is compared or stored, the standard’s CanonicalizeKeyedCollectionKey step turns -0 into +0, so get(0) finds the entry and the stored key is not -0.

Equality: ==, === and Object.is compares SameValueZero with the other three ways JavaScript has of asking whether two values are the same.

Insertion order and iteration

An ordinary object does not list its keys in the order you wrote them: keys that look like array indexes come first, in ascending numeric order, and only then the other strings, in insertion order, as Object.keys, entries, fromEntries and key order shows. A Map has one rule for every key:

Order in an object and in a Map JavaScript · insertion-order.mjs
// An object lists integer-like keys first, in ascending order; a Map keeps insertion order for every key.
const asObject = { 10: 'Ravi', 2: 'Meena', b: 'Kiran', a: 'Asha' };
const asMap = new Map([[10, 'Ravi'], [2, 'Meena'], ['b', 'Kiran'], ['a', 'Asha']]);
console.log('object:', Object.keys(asObject).join(' '));
console.log('map:   ', [...asMap.keys()].join(' '));

asMap.set(2, 'Meena K');                 // an existing key: the value changes, the position stays
asMap.delete(10);
asMap.set(10, 'Ravi');                   // deleted, then set again: it goes to the end
for (const [key, value] of asMap) console.log(key, '->', value);

// forEach passes the value first, then the key.
asMap.forEach((value, key) => console.log(`forEach(value = ${value}, key = ${key})`));

Output

object: 2 10 b a
map:    10 2 b a
2 -> Meena K
b -> Kiran
a -> Asha
10 -> Ravi
forEach(value = Meena K, key = 2)
forEach(value = Kiran, key = b)
forEach(value = Asha, key = a)
forEach(value = Ravi, key = 10)

Recorded with Node.js 26.10.0 on macOS 26 arm64. To run it yourself: mise exec node@26.10.0 -- node insertion-order.mjs

The standard describes a Map as a list of entries, and that picture explains every line of the output:

A Map as a list of entries in insertion order: set updates in place or appends, delete leaves an empty slot.A Map's entries, in insertion order1. 411001 → 'Pune GPO'2. 110001 → 'New Delhi GPO'set(110001, …) changes the value here3. empty: 600001 was deletedloops skip it; no entry moves up4. 560001 → 'Bengaluru GPO'next: set(400001, …) adds it herea new key always goes after the othersfor…of, keys(), values() and forEach go from 1 to 4

How the standard describes a Map's entries

Text description of the diagram

A column of five boxes shows the entries of a Map of PIN codes, from top to bottom in the order their keys were added.

  1. Entry 1 holds the key 411001 with the value 'Pune GPO'.
  2. Entry 2 holds 110001 with 'New Delhi GPO'. A note says that `set(110001, …)`, a key the Map already has, changes the value in this entry.
  3. Entry 3 is empty, drawn with a dashed border: its key, 600001, was deleted. Loops skip it, and no entry moves up to fill the gap.
  4. Entry 4 holds 560001 with 'Bengaluru GPO'.
  5. The last box, dashed, is where `set(400001, …)` adds its new entry: a new key always goes after all the others.

A line under the boxes says that `for…of`, `keys()`, `values()` and `forEach` visit the entries from 1 to 4.

  • Setting a key the Map already has changes the value in its entry, so 2 keeps its place after set(2, 'Meena K').
  • Deleting a key empties its entry, and setting it again creates a new entry at the end, which is why 10 moved from first to last.
  • for…of walks the entries in order and gives a [key, value] array each time, ready to destructure. keys(), values() and entries() return iterators over the keys, the values or the pairs.
  • forEach passes the value first and the key second, the reverse of the pairs that for…of gives. Mixing the two up is a common source of bugs that silently swap keys and values.

Because a loop walks the entries by position, changing the Map while you loop is defined behaviour: an entry deleted before the loop reaches it is skipped, and an entry added during the loop is visited too. That also means a loop that adds a new key on every step never ends.

Converting to and from objects, arrays and JSON

Maps and objects convert into each other through arrays of pairs, and JSON needs a deliberate step:

Objects, entries, JSON and Map.groupBy JavaScript · map-conversions.mjs
const fares = { sleeper: 455, '3A': 1210, '2A': 1745 };

// Object → Map: Object.entries gives [key, value] pairs, which is what new Map takes.
const fareMap = new Map(Object.entries(fares));
console.log('get 3A:', fareMap.get('3A'));

// Map → array of entries, and back to an object.
console.log('entries:', JSON.stringify([...fareMap]));
console.log('object:', JSON.stringify(Object.fromEntries(fareMap)));

// JSON.stringify sees a Map as an object with no own properties.
console.log('stringify(map):', JSON.stringify(fareMap));

// Object.fromEntries turns every key into a string, so 1 and '1' collide.
const mixed = new Map([[1, 'number one'], ['1', 'string one']]);
console.log('map size:', mixed.size, ' object:', JSON.stringify(Object.fromEntries(mixed)));

// Map.groupBy (ES2024) builds a Map of arrays from a list.
const payments = [
  { id: 'p1', status: 'success', amount: 250 },
  { id: 'p2', status: 'failed', amount: 99 },
  { id: 'p3', status: 'success', amount: 1200 },
];
const byStatus = Map.groupBy(payments, (p) => p.status);
for (const [status, list] of byStatus) console.log(status, list.map((p) => p.id).join(', '));

Output

get 3A: 1210
entries: [["sleeper",455],["3A",1210],["2A",1745]]
object: {"sleeper":455,"3A":1210,"2A":1745}
stringify(map): {}
map size: 2  object: {"1":"string one"}
success p1, p3
failed p2

Recorded with Node.js 26.10.0 on macOS 26 arm64. To run it yourself: mise exec node@26.10.0 -- node map-conversions.mjs

  • new Map(Object.entries(obj)) turns an object’s own enumerable string keys into a Map, and Object.fromEntries(map) turns a Map back into an object.
  • [...map] (or Array.from(map)) gives the array of [key, value] pairs, which also works with sort, filter and the other array methods.
  • JSON.stringify(map) gives "{}". A Map keeps its entries in an internal slot, not in properties, so the JSON has none of them, and there is no error to warn you. To send a Map as JSON, send [...map], or Object.fromEntries(map) when every key is a string.
  • Object.fromEntries turns every key into a string, so distinct keys of a Map can collide: the number 1 and the string '1' became one property, and the later value won.
  • Map.groupBy(items, fn) (ES2024) sorts a list into a Map of arrays, keyed by whatever fn returns; unlike Object.groupBy, the keys keep their type.

The last lesson of this module, Choosing between objects, arrays, Map and Set, shows how to send Maps and Sets through JSON and get them back.

getOrInsert: one call instead of has, set and get

Code that collects values under a key repeats the same three steps: check whether the key is there, store an empty starting value if it is not, then read the value back. getOrInsert(key, value) does all three: if the Map has the key, it returns the stored value and changes nothing; otherwise it stores value under the key and returns it:

Counting and grouping with getOrInsert JavaScript · word-count.mjs
// getOrInsert is new in ES2026. Where an engine lacks it (Node.js 24, Chrome before 145), the helper does the same steps.
function getOrInsert(map, key, value) {
  if (typeof map.getOrInsert === 'function') return map.getOrInsert(key, value);
  if (!map.has(key)) map.set(key, value);
  return map.get(key);
}

const cities = ['Pune', 'Delhi', 'Pune', 'Kochi', 'Delhi', 'Pune'];

// Counting: a number cannot change in place, so the new count is stored with set.
const counts = new Map();
for (const city of cities) counts.set(city, getOrInsert(counts, city, 0) + 1);
console.log('counts:', [...counts].map(([city, n]) => `${city} ${n}`).join(', '));

// Grouping: getOrInsert returns the array that is stored, and push changes it in place.
const orders = [['Pune', 'A-101'], ['Delhi', 'B-7'], ['Pune', 'A-102']];
const byCity = new Map();
for (const [city, order] of orders) getOrInsert(byCity, city, []).push(order);
console.log('by city:', JSON.stringify([...byCity]));

// The same grouping without getOrInsert takes has, set and get.
const byHand = new Map();
for (const [city, order] of orders) {
  if (!byHand.has(city)) byHand.set(city, []);
  byHand.get(city).push(order);
}
console.log('same result by hand:', JSON.stringify([...byHand]) === JSON.stringify([...byCity]));
console.log('getOrInsert built in:', typeof Map.prototype.getOrInsert === 'function');

Output

counts: Pune 3, Delhi 2, Kochi 1
by city: [["Pune",["A-101","A-102"]],["Delhi",["B-7"]]]
same result by hand: true
getOrInsert built in: true

Recorded with Node.js 26.10.0 on macOS 26 arm64. To run it yourself: mise exec node@26.10.0 -- node word-count.mjs

The two halves of the example show where it helps:

  • Grouping, when the value is an array or an object that you change in place: getOrInsert(city, []).push(order) returns the array that is in the Map, new or old, and push changes it there. Four lines of has, set and get become one.
  • Counting, when the value is a number: a number cannot change in place, so the new count still has to be stored with set. The example’s getOrInsert(counts, city, 0) + 1 is just another way of writing (counts.get(city) ?? 0) + 1.
  • An existing value is never replaced, even when it is undefined: getOrInsert decides by whether the key is there, not by what is stored under it.

Where getOrInsert works

getOrInsert and getOrInsertComputed are part of ES2026. MDN’s compatibility data lists them in Chrome 145, Firefox 144, Safari 26.2, Node.js 26 and Deno 2.6.7, so this lesson’s examples run on Node.js 26.10.0; the QuickJS engine behind the Run buttons has them too. Node.js 24 and older browsers do not, and calling them there throws a TypeError. The getOrInsert helper at the top of the example works everywhere: it uses the built-in method when there is one and the same three steps when there is not.

getOrInsertComputed: a default made only when needed

JavaScript evaluates every argument before it calls a function, so getOrInsert(word, []) builds a new empty array on every call, even when the key is there and the array is thrown away at once. getOrInsertComputed(key, fn) takes a function instead and calls it only when the key is missing:

When the default is made JavaScript · computed-once.mjs
// getOrInsert evaluates its default on every call; getOrInsertComputed calls its function only for a missing key.
let made = 0;
const newBucket = () => {
  made += 1;
  return [];
};

const eager = new Map();
for (const word of ['chai', 'chai', 'chai']) eager.getOrInsert(word, newBucket()).push(word);
console.log('getOrInsert: arrays made', made, ' kept', eager.size);

made = 0;
const lazy = new Map();
for (const word of ['chai', 'chai', 'chai']) lazy.getOrInsertComputed(word, newBucket).push(word);
console.log('getOrInsertComputed: arrays made', made, ' kept', lazy.size);

// The function receives the key, so one function serves every key.
const slugs = new Map();
const slugify = (title) => title.toLowerCase().replaceAll(' ', '-');
console.log(slugs.getOrInsertComputed('Masala Dosa', slugify), slugs.getOrInsertComputed('Masala Dosa', () => 'not called'));

// The key is canonicalised first (-0 becomes 0) and is the only argument; this is undefined.
const zeros = new Map();
zeros.getOrInsertComputed(-0, function (key, ...rest) {
  console.log('key is -0:', Object.is(key, -0), ' more arguments:', rest.length, ' this:', this);
  return 'zero';
});

// If the function stores the same key itself, its return value replaces what it stored.
const notes = new Map();
const value = notes.getOrInsertComputed('k', (key) => {
  notes.set(key, 'stored inside');
  return 'returned';
});
console.log('returned:', value, ' stored:', notes.get('k'), ' size:', notes.size);

// The function must be callable even when the key is already there.
try {
  new Map([['k', 1]]).getOrInsertComputed('k', 'not a function');
} catch (error) {
  console.log('a non-function throws', error.name);
}

Output

getOrInsert: arrays made 3  kept 1
getOrInsertComputed: arrays made 1  kept 1
masala-dosa masala-dosa
key is -0: false  more arguments: 0  this: undefined
returned: returned  stored: returned  size: 1
a non-function throws TypeError

Recorded with Node.js 26.10.0 on macOS 26 arm64. To run it yourself: mise exec node@26.10.0 -- node computed-once.mjs

For three copies of one word, getOrInsert built three arrays and kept one; getOrInsertComputed built one. With millions of keys, or a default that is expensive to make (a parsed template, a database handle, a Set per user), that difference is real work saved. The function receives the key, so one function such as slugify can serve every key. The standard spells out four details, and the example checks each of them:

  1. The second argument must be a function, even when it is not needed. A string throws a TypeError although the key is there, so a mistake shows up on the first call rather than on the first miss.
  2. The key is canonicalised first: -0 becomes 0 before the function sees it, exactly as for set.
  3. The function gets one argument, the key, and this is undefined, so a method passed on its own loses its object, as it does with any callback.
  4. If the function stores the same key itself, its return value wins. After the function returns, the Map looks for the key again and overwrites the value it finds, so there is never a second entry for one key. Code that relies on this is hard to read: keep the function from changing the same Map.

Map or plain object?

A Map and a plain object as a dictionary
Criterion Plain objectMap
Key types Strings and symbols; any other key becomes a stringAny value, kept as it is
Keys present at the start Inherited names such as constructor; __proto__ is specialNone
Order Index-like keys first, then insertion orderInsertion order for every key
Number of entries Object.keys(obj).length builds an arraymap.size
JSON Written and read as it isNeeds [...map] or Object.fromEntries
Fixed field names known in the code Natural: obj.priceAwkward: map.get('price')
When to choose Records with known fields, and JSON dataDictionaries whose keys come from data

A plain object is the right shape for a record, a thing with a fixed set of named fields such as { id, name, price }, and for data that travels as JSON. A Map is the right shape for a dictionary, where the keys are values that arrive at run time: user ids, words in a text, PIN codes, objects. The keys of an object also include names it inherits, so 'constructor' in {} is true, and assigning to the key '__proto__' calls a setter instead of storing a value: harmless when you write the keys yourself, a bug when users choose them. A Map starts empty and stores any key as data. How fast each one is depends on the engine, the size and the operation; the last lesson of this module measures both instead of guessing.

Word Counter See the word frequencies of a pasted text, the same count that frequencies() below builds with a Map. JavaScript & TypeScript Online Compiler Try your own keys, orders and conversions in a full editor.

Exercise: words by frequency

Exercise · Medium · JavaScript

Count words into a Map, most frequent first

A search box logs the words people type, and the team wants to see which words come up most. Write frequencies(words), which counts how often each string appears in the array words and returns a Map from each word to its count:

  • the entries come in order of count, the highest count first;
  • words with the same count come in ascending order of their code units, the order that < gives for strings (so 'B' comes before 'a', and '10' before '2');
  • every string counts as it is: 'Chai' and 'chai' are two different words;
  • an empty array gives an empty Map, and the array you are given is not changed.
frequencies(['chai', 'dosa', 'chai'])        // Map(2) { 'chai' => 2, 'dosa' => 1 }
frequencies(['pune', 'agra', 'agra', 'pune']) // Map(2) { 'agra' => 2, 'pune' => 2 }

The starter code counts correctly but returns the words in the order it first met them: run the sample tests to see which ones that fails. The engine of the Run button has getOrInsert, so you may use it.

Starter code · frequencies.mjs

/**
 * Counts how often each string appears in words. Returns a Map from word to count, the highest count first, and words
 * with equal counts in ascending code-unit order.
 */
export function frequencies(words) {
  const counts = new Map();
  for (const word of words) counts.set(word, (counts.get(word) ?? 0) + 1);
  // Replace this line: the entries are still in the order the words first appeared.
  return counts;
}
The sample tests · frequencies.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { frequencies } from './frequencies.mjs';

test('counts every word', () => {
  assert.deepEqual([...frequencies(['chai', 'dosa', 'chai'])], [['chai', 2], ['dosa', 1]]);
  assert.deepEqual([...frequencies(['idli'])], [['idli', 1]]);
});

test('the highest count comes first', () => {
  assert.deepEqual([...frequencies(['a', 'b', 'b', 'c', 'c', 'c'])], [['c', 3], ['b', 2], ['a', 1]]);
  assert.deepEqual([...frequencies(['x', 'y', 'y', 'x', 'y'])], [['y', 3], ['x', 2]]);
});

test('equal counts are in code-unit order', () => {
  assert.deepEqual([...frequencies(['pune', 'delhi', 'agra'])], [['agra', 1], ['delhi', 1], ['pune', 1]]);
  assert.deepEqual([...frequencies(['b', 'a', 'B'])], [['B', 1], ['a', 1], ['b', 1]]);
  assert.deepEqual([...frequencies(['2', '10', '1', '2', '10'])], [['10', 2], ['2', 2], ['1', 1]]);
});

test('words that are names of object properties count like any other', () => {
  const words = ['constructor', '__proto__', 'toString', '__proto__'];
  assert.deepEqual([...frequencies(words)], [['__proto__', 2], ['constructor', 1], ['toString', 1]]);
});

test('an empty array gives an empty Map', () => {
  const result = frequencies([]);
  assert.ok(result instanceof Map, 'the result is a Map');
  assert.equal(result.size, 0);
});

test('the input array is not changed', () => {
  const words = ['tea', 'coffee', 'tea'];
  frequencies(words);
  assert.deepEqual(words, ['tea', 'coffee', 'tea']);
});
A hint

Count first, then sort: [...counts] gives an array of [word, count] pairs. Sort it with a comparison that looks at the counts first (b[1] - a[1] puts the larger count first) and, only when the counts are equal, compares the words with < and >. new Map(pairs) keeps the order of the array it is given.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 What does this code print?

    Read the code, then choose one answer.

    const m = new Map([[1, 'a']]);
    m.set('1', 'b');
    console.log(m.size, m.get(1));
    Show the answer to question 1

    Answer: 2 a

    A Map keeps keys exactly as given, so the number 1 and the string '1' are two keys and set('1', 'b') adds a second entry. get(1) still finds the value stored under the number. A plain object would have turned both keys into '1' and kept only 'b'.

  2. Question 2 of 6 byCity is a Map. Which line adds order to the array stored under city, creating the array the first time?

    Choose one answer.

    Show the answer to question 2

    Answer: byCity.getOrInsert(city, []).push(order)

    getOrInsert returns the array already stored under the key, or stores the new empty array and returns that. set returns the Map, which has no push; get ignores its second argument and returns undefined for a new city; and getOrInsertComputed needs a function such as () => [], so an array throws a TypeError.

  3. Question 3 of 6 What does this code print?

    Read the code, then choose one answer.

    const stock = new Map([['tea', 12]]);
    stock.forEach((a, b) => console.log(a, b));
    Show the answer to question 3

    Answer: 12 tea

    forEach calls its function with the value first and the key second, whatever the parameters are named. A for…of loop over the same Map gives ['tea', 12] pairs, key first.

  4. Question 4 of 6 m is a new, empty Map. Which of these statements are true? Choose every one.

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • After m.set(-0, 'z'), m.get(0) is 'z'.
    • After m.set(NaN, 'x'), m.get(NaN) is 'x'.

    Map keys are compared with SameValueZero, so NaN finds itself and -0 is stored as 0. Arrays are keys by identity, and the second [1] is a new array, so get returns undefined. JSON.stringify writes a Map as {}, and size is a property: calling it throws a TypeError.

  5. Question 5 of 6 What does this code print?

    Read the code, then choose one answer.

    let calls = 0;
    const make = () => { calls++; return 0; };
    const m = new Map();
    for (const k of ['a', 'b', 'a', 'a']) m.getOrInsertComputed(k, make);
    console.log(calls, m.size);
    Show the answer to question 5

    Answer: 2 2

    getOrInsertComputed calls its function only when the key is missing: once for 'a' and once for 'b'. The Map ends with two keys. With m.getOrInsert(k, make()), make would run four times, because an argument is evaluated before every call.

  6. Question 6 of 6 What does this code print?

    Read the code, then choose one answer.

    const m = new Map([['x', 1], ['y', 2], ['z', 3]]);
    m.set('x', 10);
    m.delete('y');
    m.set('y', 20);
    console.log([...m.keys()].join(''));
    Show the answer to question 6

    Answer: xzy

    Setting 'x', a key the Map has, changes its value and keeps its place. Deleting 'y' and setting it again creates a new entry at the end, after 'z'.

Interview questions

When do you choose a Map over a plain object for key–value data? Choose a Map when the keys come from data rather than from your code. A Map takes keys of any type and keeps them apart, so 1 and '1' are two keys and an object is a key by identity, while a plain object turns every key into a string. A Map starts empty, whereas an object inherits names such as constructor and treats __proto__ specially, which matters as soon as users choose the keys. A Map also keeps every key in insertion order and reports its size directly. Choose a plain object for records whose field names you know in advance, such as { id, name, price }, and for data that goes to and from JSON as it is, since JSON.stringify writes a Map as {}.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A log processor groups 5 million lines by user id with if (!byUser.has(id)) byUser.set(id, []); byUser.get(id).push(line). A pull request replaces that with byUser.getOrInsert(id, []).push(line). Review it: what stays the same, what costs more, what can break, and what would you merge instead?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A cinema booking service records reserved seats with reserved.set([row, seat], bookingId). Every later reserved.has([7, 12]) is false, so seats get booked twice. Explain why, then compare three fixes: a string key, nested Maps and one number per seat.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A Map stores keys of any type exactly as given: 1 and '1' are two keys, objects are keys by identity, NaN finds itself and -0 is stored as 0.
  • Keys keep their insertion order; setting an existing key changes the value in place, and deleting and setting it again moves it to the end.
  • forEach passes (value, key), while for…of gives [key, value] pairs.
  • JSON.stringify writes a Map as {}: convert it with [...map] or Object.fromEntries(map) first, and remember that Object.fromEntries turns keys into strings.
  • getOrInsert(key, value) returns the stored value or stores the default; getOrInsertComputed(key, fn) calls fn only for a missing key, so use it when the default costs something to make.
  • Both methods are ES2026: Node.js 26 and current browsers have them, Node.js 24 does not, and a three-line helper covers the gap.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.