JavaScript Array Methods Cheatsheet: map, filter, reduce, and the Modern Toolkit

Link copied
JavaScript Array Methods Cheatsheet: map, filter, reduce, and the Modern Toolkit

JavaScript Array Methods Cheatsheet: map, filter, reduce, and the Modern Toolkit

Arrays are the second most-used data structure in JavaScript. The language ships dozens of array methods — far more than most developers use day to day. This lesson walks through the practical toolkit: what each method does, when to reach for it, and which methods to use instead of explicit loops (we covered loops in Lesson 1.6 — most modern array code is map/filter/reduce, not for).

By the end you'll know which method fits which problem and which seven or eight you'll reach for in 95% of code.

The big idea: arrays as values, not loops #

The shift from "loop through an array and do things" to "transform an array into a new array" is the defining mental model of modern JavaScript.

// Imperative: explicit loop
const doubled = [];
for (const n of nums) doubled.push(n * 2);

// Declarative: describe the transformation
const doubled = nums.map(n => n * 2);

The second version is shorter, doesn't need a mutable accumulator, composes (you can .filter().map().reduce()), and reads more like the intent than the mechanics. That's the win.

Group 1: Transformers (return a new array) #

These take an array and return a new one of the same shape (possibly different size or value type).

map(fn) — transform every element #

[1, 2, 3].map(n => n * 2);          // [2, 4, 6]
users.map(u => u.name);              // ['Ada', 'Grace']

The return value of the callback becomes the new element. Always returns an array of the same length.

filter(predicate) — keep matching elements #

[1, 2, 3, 4].filter(n => n % 2 === 0); // [2, 4]
users.filter(u => u.active);            // only active users

The predicate returns truthy/falsy. Truthy elements survive. Length may be anything ≤ the original.

flatMap(fn) — map then flatten one level #

['hi there', 'and you'].flatMap(s => s.split(' '));
// ['hi', 'there', 'and', 'you']

// Filtering and mapping at once by returning [] or [value]
users.flatMap(u => u.active ? [u.name] : []);

Useful when each element might produce zero, one, or many output elements.

slice(start, end) — extract a portion (non-mutating) #

[1, 2, 3, 4, 5].slice(1, 3);   // [2, 3]
[1, 2, 3, 4, 5].slice(-2);     // [4, 5]  — from end
[1, 2, 3, 4, 5].slice();       // [1, 2, 3, 4, 5] — full shallow copy

Returns a new array. Doesn't change the original. Use slice() (no args) as a quick shallow clone.

concat(arr) — combine arrays (non-mutating) #

[1, 2].concat([3, 4]); // [1, 2, 3, 4]

Mostly replaced by spread: [...a, ...b]. Both are fine; spread is more flexible.

Group 2: Reducers (return a single value) #

reduce(fn, initial) — combine into one value #

[1, 2, 3, 4].reduce((sum, n) => sum + n, 0); // 10

users.reduce((acc, u) => {
  acc[u.id] = u;
  return acc;
}, {}); // index users by id

Reduce is the most powerful and the most often-misused. The callback gets (accumulator, element, index, array). The initial value (0, {}, []) seeds the accumulator.

If your reduce can be expressed as one of the more specific methods below — use the specific one. It reads more clearly.

reduceRight(fn, initial) — like reduce, but right-to-left #

[1, 2, 3].reduceRight((acc, n) => acc + n, '');  // '321' as a string

Rare. Mostly for compose-like operations where order matters.

Group 3: Searchers (return one element or a boolean) #

find(predicate) — first match #

users.find(u => u.id === 42);  // the user, or undefined

findIndex(predicate) — first match's index #

users.findIndex(u => u.id === 42);  // index, or -1

findLast(predicate) / findLastIndex(predicate) — last match #

logs.findLast(l => l.level === 'error'); // most recent error

Added in ES2023. Saves writing arr.slice().reverse().find(...).

some(predicate) — any match? #

users.some(u => u.role === 'admin'); // boolean

Short-circuits on first truthy callback. Useful in conditions: if (users.some(...)).

every(predicate) — all match? #

responses.every(r => r.ok); // true if every one passed

Short-circuits on first falsy callback. The companion to some.

includes(value) — contains this exact value? #

[1, 2, 3].includes(2);             // true
['a', 'b'].includes('c');          // false
[NaN].includes(NaN);               // true (unlike indexOf)

Uses SameValueZero equality — NaN matches NaN. Prefer over indexOf(x) !== -1.

indexOf(value) / lastIndexOf(value) — position #

names.indexOf('Ada');     // index, or -1
names.lastIndexOf('Ada'); // last occurrence, or -1

Useful when you need the position, not just "is it in there."

Group 4: Side-effect iteration #

forEach(fn) — visit every element #

users.forEach(u => console.log(u.name));

Returns undefined. Used for side effects only. Cannot be broken out of — there's no break or return from forEach. If you need early exit, use for...of.

In modern code, forEach is fading. Prefer for...of for side effects (it allows break/continue) and map/filter/reduce for everything else.

Group 5: Mutators (modify the original) #

These change the array in place. Use sparingly — immutable patterns are easier to reason about.

arr.push(x);            // append to end, returns new length
arr.pop();              // remove and return last
arr.unshift(x);         // prepend, returns new length
arr.shift();            // remove and return first
arr.splice(i, n, ...);  // remove n at index i, insert items there
arr.reverse();          // reverse in place
arr.sort(fn);           // sort in place
arr.fill(value);        // fill all slots with value
arr.copyWithin(...);    // copy a section to another part

Immutable equivalents (ES2023) #

Four methods that do the same thing but return a new array:

arr.toReversed();       // new reversed array, original unchanged
arr.toSorted(fn);       // new sorted array
arr.toSpliced(i, n, ...); // new array with the splice applied
arr.with(i, value);     // new array with arr[i] replaced

Use these in any code that should not mutate (React/Redux/Angular state, function arguments). The mutating originals still work but require defensive slice() calls to be safe.

Group 6: Sorting #

[3, 1, 4, 1, 5].sort();           // [1, 1, 3, 4, 5]
['banana', 'apple'].sort();        // ['apple', 'banana']

// Numbers — gotcha: default sort is lexicographic
[10, 2, 30].sort();                // [10, 2, 30] — WRONG, treated as strings
[10, 2, 30].sort((a, b) => a - b); // [2, 10, 30] — correct

Always pass a comparator for numbers. The callback returns:

  • Negative → a before b
  • Zero → unchanged order
  • Positive → b before a

a - b for ascending, b - a for descending.

Sorting objects #

users.sort((a, b) => a.age - b.age);             // by age
users.sort((a, b) => a.name.localeCompare(b.name)); // by name (locale-aware)

localeCompare handles accents, capitalization, and language-specific rules correctly. Plain a < b does not.

Composing methods #

The real power: chain them.

const topActiveAdmins = users
  .filter(u => u.active)
  .filter(u => u.role === 'admin')
  .sort((a, b) => b.score - a.score)
  .slice(0, 5)
  .map(u => u.name);

Reads top-to-bottom like the pipeline it is. Each step makes a new array; the original users is never touched.

For very long pipelines or one-shot transforms over huge arrays, the intermediate arrays can be expensive. Two solutions:

  1. Combine into a single reduce when readability allows.
  2. Use generators or libraries (lazy.js, native Iterator.from()) for lazy chains.

For most code, multiple intermediate arrays are fine.

When to drop the methods and write a loop #

Three situations:

  • Early exit needed. for...of lets you break; forEach and friends don't.
  • Asynchronous per-element work. Use for...of with await (sequentially) or Promise.all(arr.map(...)) (in parallel). forEach(async ...) does NOT wait.
  • Performance-critical hot path. Hand-rolled for loops are still slightly faster on engines.

A cheatsheet #

Goal Method
New array, same length, different values map
New array, fewer elements filter
Map + flatten flatMap
Combine to one value reduce
Find first match find
Find last match findLast
Find index findIndex / findLastIndex
Test membership includes
Any/all match some / every
Slice a portion slice
Combine arrays spread [...a, ...b]
Sort (immutable) toSorted
Reverse (immutable) toReversed
Side-effect each for...of

Memorize that table and 95% of your array work flows naturally.

What's next #

Lesson 3.3 covers spread and destructuring..., [a, b] = arr, {x, y} = obj, the patterns that pair with arrays and objects to make modern data manipulation concise. After that, you'll have the full primitive-vs-object toolkit.

Try it yourself #

The sort-default-is-lexicographic bug is the most common array-method footgun. Predict the result:

YouPredict the output:
console.log([1, 10, 2, 21, 3].sort());
Claude · used js_sandboxOutput: [1, 10, 2, 21, 3].

The default sort converts every element to a string and compares lexicographically — '10' comes before '2' because the character '1' is less than '2'.

To sort numerically: [1,10,2,21,3].sort((a,b) => a - b)[1, 2, 3, 10, 21].

One missing comparator, completely wrong order. Always pass one for numbers.

Up next in JavaScript

More from this topic

View all JavaScript articles →

Enjoyed this article?

Get new JavaScript tutorials delivered. No spam — just code-first articles when they ship.

Leave a Comment

Your email address will not be published. Required fields are marked *