JavaScript Array Methods Cheat Sheet: map, filter, reduce & More

Map

Direct answer: Use map to transform every element, filter to keep some, reduce to collapse many into one, and find/some/every to ask questions without building a new array. Since ES2023, toSorted, toReversed, toSpliced and with return 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

Decision tree for choosing a JavaScript array method Start from what you need: a new array of same length (map), a shorter array (filter), one value (reduce), a single element (find), a yes/no answer (some or every), or just side effects (forEach or for…of). What do you need back? Same length,transformed Fewer items,same shape One value frommany One elementor its position A yes / noanswer mapfilterreduce find / findIndexsome / every returns new arrayreturns new array returns anythingundefined / -1 if none boolean, short-circuits Only side effects (logging, DOM writes)? forEach, or for…of if you need break, continue or await Never use map just to loop: it allocates an array you throw away.

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

js

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);   // 6

The failure mode is the empty array with no initial value:

js

[].reduce((a, b) => a + b);      // TypeError[].reduce((a, b) => a + b, 0);   // 0

Always 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

MethodReturnsMutates?Notes
find(fn)first match or undefinedNoStops at first hit
findIndex(fn)index or -1NoCheck -1 before using as an index
findLast / findLastIndexlast match/indexNoES2023
some(fn)booleanNoStops at first true
every(fn)booleanNoStops at first false; true on an empty array
includes(x)booleanNoFinds NaN; indexOf does not
flat(depth)new arrayNoDefault depth 1; Infinity for full flatten
flatMap(fn)new arrayNomap then flat(1); also a one-pass map-and-filter
at(i)elementNoAccepts negatives: at(-1) is the last item
forEach(fn)undefinedNoCannot be stopped early or awaited
Array.fromAsync(src)Promise<array>n/aSequentially 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);                    // 3

some 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 originalReturns a copy
sort, reverse, splice, push, pop, shift, unshift, fill, copyWithintoSorted, 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

  1. Pass an arrow, not a bare function reference, to map, filter and reduce unless you know the callback’s arity.
  2. Always give reduce an initial value.
  3. Use some/every/find for existence checks, not filter(...).length.
  4. Prefer toSorted/toReversed over in-place versions on shared data.
  5. Replace forEach with for...of the moment you need await, break or return.

Next step: search your codebase for .sort( and .map(parseInt and check each hit against the rules above.

Leave a Comment