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 →
abeforeb - Zero → unchanged order
- Positive →
bbeforea
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:
- Combine into a single
reducewhen readability allows. - Use generators or libraries (
lazy.js, nativeIterator.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...oflets youbreak;forEachand friends don't. - Asynchronous per-element work. Use
for...ofwithawait(sequentially) orPromise.all(arr.map(...))(in parallel).forEach(async ...)does NOT wait. - Performance-critical hot path. Hand-rolled
forloops 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:
console.log([1, 10, 2, 21, 3].sort());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
Enjoyed this article?
Get new JavaScript tutorials delivered. No spam — just code-first articles when they ship.


