
Direct answer: Use
mapto transform every element,filterto keep some,reduceto collapse many into one, andfind/some/everyto ask questions without building a new array. Since ES2023,toSorted,toReversed,toSplicedandwithreturn copies, so you can drop the[...arr].sort()workaround.
Run ['1','2','3'].map(parseInt) in any console and you get [1, NaN, NaN]. map passes (value, index, array) to the callback, and parseInt reads the index as its radix argument. Most array bugs in production code come from the same place: a callback that receives more arguments than the author assumed, or a method that mutates when the author expected a copy.
This cheat sheet is organized around those two failure classes. Every output below was run on Node.js v22.22.2 on October 19, 2026.
The mental model: one question decides the method
The diagram is a shortcut for the rule: pick the method by the shape of the return value, not by habit. The sections below cover what each method does and where it breaks.
The big three, with their traps
map: transform every element
const prices = [10, 20, 30];prices.map(p => p * 1.2); // [12, 24, 36]The trap is the extra arguments. Pass a function reference only when you know its full signature.
js
['1','2','3'].map(parseInt); // [1, NaN, NaN]['1','2','3'].map(Number); // [1, 2, 3]When in doubt, write the arrow: .map(s => parseInt(s, 10)).
filter: keep what passes
js
const nums = [1, 2, 3, 4];nums.filter(n => n % 2 === 0); // [2, 4]filter(Boolean) strips 0, '', null, undefined and NaN. That includes legitimate zeros, so don’t use it on numeric data without thinking.
Chaining is fine for small arrays. arr.map(f).filter(g) allocates an intermediate array; on large data, one reduce or a single loop avoids it. Measure before optimizing: I did not benchmark this, and engine behavior varies.
reduce: collapse to one value
js
[1, 2, 3].reduce((acc, n) => acc + n); // 6The failure mode is the empty array with no initial value:
js
[].reduce((a, b) => a + b); // TypeError[].reduce((a, b) => a + b, 0); // 0Always pass the initial value unless you can prove the array is non-empty. That single habit prevents the most common reduce crash.
reduce shines for grouping and indexing:
js
const rows = [{k:'a',v:1},{k:'b',v:2},{k:'a',v:3}];rows.reduce((m, {k, v}) => { (m[k] ??= []).push(v); return m; }, {});// { a: [1, 3], b: [2] }The same job is now one call in modern runtimes: Object.groupBy(rows, r => r.k). It is a static method on Object, not an array method, and it returns a null-prototype object. Check your target runtimes before relying on it.
My position: reach for reduce when the result is a single scalar or an index. If the accumulator is an array you are pushing into, you probably wanted map, filter or flatMap, and the code will read better.
The rest of the toolkit
| Method | Returns | Mutates? | Notes |
|---|---|---|---|
find(fn) | first match or undefined | No | Stops at first hit |
findIndex(fn) | index or -1 | No | Check -1 before using as an index |
findLast / findLastIndex | last match/index | No | ES2023 |
some(fn) | boolean | No | Stops at first true |
every(fn) | boolean | No | Stops at first false; true on an empty array |
includes(x) | boolean | No | Finds NaN; indexOf does not |
flat(depth) | new array | No | Default depth 1; Infinity for full flatten |
flatMap(fn) | new array | No | map then flat(1); also a one-pass map-and-filter |
at(i) | element | No | Accepts negatives: at(-1) is the last item |
forEach(fn) | undefined | No | Cannot be stopped early or awaited |
Array.fromAsync(src) | Promise<array> | n/a | Sequentially awaits async iterables |
Verified outputs:
js
[1,2,3].find(x => x > 5); // undefined[1,2,3].findIndex(x => x > 5); // -1[1,2,3,4].findLast(x => x % 2); // 3[NaN].includes(NaN); // true[NaN].indexOf(NaN); // -1[1,[2,[3]]].flat(); // [1, 2, [3]][1,[2,[3]]].flat(Infinity); // [1, 2, 3][1,2,3].at(-1); // 3some and every stop early. In my run, [1,2,3].some(x => x === 1) invoked the callback once. That makes them the right tool for existence checks on large arrays, and find followed by a truthiness test is the wrong one, since a found value of 0 is falsy.
Mutation: the second bug class
| Mutates the original | Returns a copy |
|---|---|
sort, reverse, splice, push, pop, shift, unshift, fill, copyWithin | toSorted, toReversed, toSpliced, with, slice, concat, map, filter |
The default sort compares as strings and mutates in place:
js
[10, 9, 1].sort(); // [1, 10, 9][10, 9, 1].toSorted((a, b) => a - b); // [1, 9, 10]The copying versions matter in React state, Redux reducers, and anywhere identity comparison drives re-renders:
js
const s = [1, 2, 3];s.toReversed(); // [3, 2, 1] (s unchanged)s.with(1, 9); // [1, 9, 3]s.toSpliced(1, 1);// [1, 3]Trade-off: these methods need an ES2023-capable runtime. Check your browser or Node targets and add a polyfill or transpile step if you support older environments.
Edge cases worth memorizing
Sparse arrays. forEach and map skip holes. In my run, [1,,3].forEach fired twice, and .map(x => x*2) returned [2, <1 empty item>, 6]. If the data comes from new Array(5), use Array.from({length: 5}, fn) instead.
forEach and await. forEach ignores the promises your callback returns, so nothing waits. Use for...of with await for sequential work, Promise.all(arr.map(...)) for parallel work, or Array.fromAsync to collect an async source.
Async mapping, sequential vs parallel. Array.fromAsync(arr, asyncFn) awaits one item at a time, while Promise.all(arr.map(asyncFn)) starts everything at once. Choose based on rate limits, not style.
Return values. A map callback with braces and no return yields an array of undefined. A filter callback that returns an object is always truthy.
Version note
Array.fromAsync reached Baseline Newly Available in January 2024 and was expected to become Widely Available on July 25, 2026, according to the web-features entry. The July 2026 Baseline digest lists it as Widely Available. The ES2023 additions (toSorted, toReversed, toSpliced, with, findLast) and Object.groupBy are documented on MDN; check the current compatibility table for your targets, because I verified behavior on Node 22 only, not in browsers.
Practical rules
- Pass an arrow, not a bare function reference, to
map,filterandreduceunless you know the callback’s arity. - Always give
reducean initial value. - Use
some/every/findfor existence checks, notfilter(...).length. - Prefer
toSorted/toReversedover in-place versions on shared data. - Replace
forEachwithfor...ofthe moment you needawait,breakorreturn.
Next step: search your codebase for .sort( and .map(parseInt and check each hit against the rules above.