Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

What does Map.prototype.getOrInsert solve?

Questions in this chapter (5)
  • Draft
  • easy
  • Askedrarely
  • Verified October 2026

Answer

The recurring pattern "read a key, create a default if missing, then use it" takes three calls and looks the key up up to three times:

if (!map.has(key)) map.set(key, []);
map.get(key).push(value);

The "upsert" methods do it in one call that returns the value:

  • map.getOrInsert(key, value) returns the existing value if the key is present; otherwise it stores value and returns it.
  • map.getOrInsertComputed(key, callback) does the same but calls callback(key) only when the key is missing and stores the result. Use it when the default is a fresh object ([], new Set()) or costly to build.

The distinction matters because arguments are evaluated first. map.getOrInsert(key, []) allocates a new array on every call, even when the key exists and the array is thrown away; getOrInsertComputed(key, () => []) allocates only on a miss.

Details from the spec:

  • Keys are compared like Map.prototype.set does, with -0 canonicalised to +0.
  • getOrInsertComputed throws a TypeError if callback is not callable. If callback itself adds that key to the map, the spec's re-check overwrites the entry with the callback's return value.
  • WeakMap has both methods, with the same CanBeHeldWeakly key rule as set.

Availability (checked, not assumed). The TC39 upsert proposal is at Stage 4, and getOrInsert / getOrInsertComputed are in the living ECMAScript specification. MDN lists them as Baseline 2026 (newly available since February 2026). In this repo's runs, Node 24.19 does not have them (typeof is undefined) and Node 26.7 does. So feature-detect, or use a small helper for older runtimes, as below. Which numbered ECMAScript edition first includes them is ❓ not verified here.

Example

// Native where available (it was in Node 26.7), the same logic over has/get/set otherwise.
const getOrInsertComputed = (map, key, callback) => {
  if (typeof map.getOrInsertComputed === 'function') return map.getOrInsertComputed(key, callback);
  if (map.has(key)) return map.get(key);
  const value = callback(key);
  map.set(key, value);
  return value;
};

const groups = new Map();
for (const word of ['apple', 'avocado', 'banana']) {
  getOrInsertComputed(groups, word[0], () => []).push(word);
}
console.log(groups);
// → Map(2) { 'a' => [ 'apple', 'avocado' ], 'b' => [ 'banana' ] }

let created = 0;
const makeList = (key) => { created++; return [key]; };
getOrInsertComputed(groups, 'a', makeList); // key exists: callback not called
getOrInsertComputed(groups, 'c', makeList); // key missing: called once
console.log(created); // → 1

Pitfalls

  • Calling getOrInsert(key, expensive()). The argument runs every time; use getOrInsertComputed(key, expensive).
  • Shipping map.getOrInsert unguarded. It throws TypeError: map.getOrInsert is not a function on Node 24 and older browsers.
  • Expecting getOrInsert to update an existing value. It never overwrites a present key; use set for that.
  • Using getOrInsert(key, value) with a stored undefined expecting a replacement. A key that is present with value undefined counts as present and returns undefined.

Follow-ups

Why not map.get(key) ?? map.set(key, d).get(key)?
It treats a stored null or undefined as missing, so it overwrites them. has or the upsert methods check for key presence, which is what you mean.
Does the callback receive the key?
Yes: callback(key). It is called with undefined as this.
What replaces Object.groupBy-style grouping loops?
Map.groupBy when you only need grouping. Use getOrInsertComputed when you build groups incrementally, such as across several passes.

Exercises

Predict the output

const map = new Map([['a', 1]]);
let calls = 0;
const makeDefault = () => { calls++; return 0; };
// Node 26+, or after adding a fallback:
map.getOrInsert('a', makeDefault());
map.getOrInsertComputed('a', makeDefault);
map.getOrInsertComputed('b', makeDefault);
console.log(calls, map.get('b'));