Map.getOrInsert vs getOrInsertComputed: which one to use, and where it fails

map.getOrInsert(key, defaultValue) returns the value stored under key, and stores defaultValue first if the key is missing. It replaces the if (!map.has(key)) map.set(key, []) lines that grouping and counting code has repeated for years. The catch is in the call itself: defaultValue is an ordinary argument, so JavaScript builds it on every call, including the calls where the key already exists. getOrInsertComputed(key, callback) is the variant that builds the value only on a miss.

The output below is from Node.js 26.13.1. Node.js 24, the older of the two long-term support (LTS) lines that production servers commonly run, has neither method.

What the two methods do differently

The first script groups five words by first letter. It counts how many times a new empty array gets built under each method.

let built = 0;
const makeList = () => { built++; return []; };
const words = ["apple", "avocado", "banana", "blueberry", "cherry"];

const eager = new Map();
for (const w of words) eager.getOrInsert(w[0], makeList()).push(w);
console.log("getOrInsert         lists built:", built);

built = 0;
const lazy = new Map();
for (const w of words) lazy.getOrInsertComputed(w[0], makeList).push(w);
console.log("getOrInsertComputed lists built:", built);
console.log(lazy);
getOrInsert         lists built: 5
getOrInsertComputed lists built: 3
Map(3) {
  'a' => [ 'apple', 'avocado' ],
  'b' => [ 'banana', 'blueberry' ],
  'c' => [ 'cherry' ]
}

Both maps end up identical. The difference is the two arrays that getOrInsert built and threw away, one for "avocado" and one for "blueberry". makeList() runs before getOrInsert is entered, so the method never gets the chance to skip it. Passing makeList without parentheses hands the function over, and getOrInsertComputed calls it with the key only when the key is absent. MDN documents the same contrast.

A throwaway empty array costs almost nothing, so getOrInsert(key, []) is fine for 0, [], "", and new Set() in a loop that runs a few thousand times. Use getOrInsertComputed when the default is a parse, a network call, a large allocation, or anything with a side effect.

Where a “default” turns into a shared object

Cost is the smaller risk. The default object is also the value that gets stored, so a default created outside the call is shared by every key that misses.

const shared = [];
const byUser = new Map();
byUser.getOrInsert("ana", shared).push("login");
byUser.getOrInsert("ben", shared).push("logout");
console.log(byUser.get("ana"), byUser.get("ana") === byUser.get("ben"));
[ 'login', 'logout' ] true

Ana’s list now contains Ben’s event because both keys point at the same array. Writing getOrInsert("ana", []) inline avoids it, since the literal makes a fresh array per call. A hoisted constant like shared, or a module-level EMPTY, does not. In a diff, check that a mutable default passed to getOrInsert is created at the call or comes from getOrInsertComputed.

What the callback may do to the map

getOrInsertComputed calls the callback with one argument (the key) and this set to undefined. If the callback throws, nothing is inserted and the error propagates. A callback that modifies the same map is the surprising case.

const cache = new Map();
const result = cache.getOrInsertComputed("k", (key) => {
  cache.set(key, "written inside the callback");
  return "returned by the callback";
});
console.log(result, "|", cache.get("k"));
returned by the callback | returned by the callback

The callback stored a value under "k" itself, and the method then replaced it with the return value. The proposal’s draft spec says why: after calling the callback it searches the map a second time, and if the key now exists it overwrites that entry’s value. So the return value of the callback wins over anything the callback stored under that same key.

A key that holds undefined counts as present. new Map([["k", undefined]]).getOrInsert("k", "fallback") returns undefined on Node.js 26.13.1, because the method tests for the key and does not test the value. Code that treats undefined as “not computed yet” still needs an explicit check.

Check the runtime before shipping this

The same script that calls getOrInsert fails on Node.js 24, which is still a supported LTS line.

const m = new Map();
console.log(typeof m.getOrInsert);
m.getOrInsert("a", 1);
undefined
TypeError: m.getOrInsert is not a function

That output is from Node.js 24.22.3 (V8 13.6). On 26.13.1 (V8 14.6) the first line prints function and the call succeeds. The 27.0.0-alpha.0.1.0 build also has both methods. Both methods come from the “upsert” proposal to TC39, the committee that maintains the JavaScript standard. It reached stage 4, the final stage that means a feature is finished and will be in the standard, at the January 2026 meeting according to the finished-proposals list, which expects it in the 2027 edition of the spec. Chrome set the shipping milestone to 145 in its Intent to Ship thread, Firefox announced version 144 in its intent to ship, and that message notes Safari 18.4 already had it. MDN marks getOrInsert as Baseline 2026 (“newly available”), its label for features that the current versions of all major browsers support.

For browser-only code that targets current browsers, the methods are usable now. For code that runs on Node.js 24 or older, or that must support older browsers, use a polyfill such as core-js, or keep the has/get/set version. A TypeScript or lint setup that targets a new lib can accept the call while the runtime still lacks it, so a test on the production Node.js version is the check that matters.

WeakMap has both methods too, with the usual restriction: a primitive key such as a string throws TypeError: Invalid value used as weak map key on Node.js 26.13.1.

Choosing between them

The examples were run with npx [email protected] and npx [email protected] as .mjs files, one per snippet. The callback-overwrite behavior was read from the spec text and confirmed in the run above.