Array Methods (forEach, map, filter, reduce...)
The built-in tools for looping over, transforming, and summarizing arrays without writing manual loops.
What is it?
You already know you can loop over an array with a for loop. But most of the time, what you're doing with that loop falls into one of a few common patterns: doing something with every item, building a new array from each item, keeping only some items, or combining everything into a single result. JavaScript gives you a built-in method for each of these patterns, so you don't have to write the loop by hand every time — and anyone reading your code recognizes the pattern from the method name alone.
The core ones you'll use constantly:
- forEach — run some code for every item (no new array)
- map — build a new array by transforming every item
- filter — build a new array keeping only items that pass a test
- find — get the first item that passes a test
- some / every — check if any / all items pass a test
- reduce — combine every item into a single value
Explain like I'm 10
Think of an assembly line. forEach is a worker who inspects every item and does something with it but hands nothing back. map is a worker who replaces every item with a new one. filter is a worker who only lets some items through. reduce is the worker at the very end who melts everything down into one final product.
Examples
forEach — run code for every item
const fruits = ["apple", "banana", "cherry"];
fruits.forEach((fruit) => {
console.log(fruit);
});
// logs: apple, banana, cherryforEach runs the callback once per item and returns undefined — it's for side effects (like logging), not for building a new value.
forEach — using the index and outer variables
const scores = [70, 85, 90];
let total = 0;
scores.forEach((score, index) => {
console.log(`Test ${index + 1}: ${score}`);
total += score;
});
console.log("Total:", total); // 245The callback also receives the index as a second argument, and can freely read and update variables from the surrounding scope — forEach itself still returns undefined either way.
map — transform every item into something new
const prices = [10, 20, 30];
const withTax = prices.map((price) => price * 1.1);
console.log(withTax); // [11, 22, 33]filter — keep only the items that pass a test
const ages = [12, 18, 25, 16, 30];
const adults = ages.filter((age) => age >= 18);
console.log(adults); // [18, 25, 30]find, some, and every — testing items
const inventory = [
{ name: "widget", stock: 0 },
{ name: "gadget", stock: 5 },
];
const outOfStock = inventory.find((item) => item.stock === 0);
console.log(outOfStock); // { name: "widget", stock: 0 }
console.log(inventory.some((item) => item.stock === 0)); // true
console.log(inventory.every((item) => item.stock > 0)); // falsereduce — combine every item into a single value
const numbers = [1, 2, 3, 4, 5];
const total = numbers.reduce((sum, n) => sum + n, 0);
console.log(total); // 15reduce — building an object (counting occurrences)
const words = ["apple", "banana", "apple", "cherry", "banana", "apple"];
const counts = words.reduce((tally, word) => {
tally[word] = (tally[word] || 0) + 1;
return tally;
}, {});
console.log(counts); // { apple: 3, banana: 2, cherry: 1 }reduce isn't just for sums — the accumulator can be any value, including an object you build up piece by piece.
How it works
Every one of these methods takes a callback function and calls it once per item, passing in the current item (and its index, and the whole array, if you need them). What differs is what each method does with the callback's return value: map collects it into a new array, filter uses it as a yes/no test, reduce feeds it back in as the accumulator for the next call, and forEach just throws it away.
[1, 2, 3].map(n => n * 2)
1 → callback(1) → 2 ┐
2 → callback(2) → 4 ├─▶ [2, 4, 6]
3 → callback(3) → 6 ┘Why does it exist?
Loops are flexible, but that flexibility is also a liability — a plain for loop can accidentally skip items, mutate things it shouldn't, or hide what it's really doing under a pile of bookkeeping (let i = 0, i++...). Naming the pattern (map, filter, reduce) makes the intent of the code obvious at a glance, and removes an entire category of off-by-one and indexing bugs.
When to use it
Reach for these whenever you're processing an array and your goal matches one of the patterns directly: transform every item (map), keep some of them (filter), summarize them into one value (reduce), or just do something with each one (forEach).
When not to use it
If you need to stop partway through for a more complex reason than "found it," or you need to loop over two arrays in lockstep, a plain for/for...of loop can be clearer than forcing it into one of these methods. And chaining many of these on a huge array does multiple full passes over the data — sometimes a single loop doing everything in one pass is more efficient.
Common mistakes
Using
forEachwhen you actually wantedmap—forEachalways returnsundefined, soconst result = arr.forEach(...)is a common bug.Forgetting to provide a starting value to
reduce, which can throw or misbehave on an empty array.Assuming
filter/mapmodify the original array — they always return a brand-new array, leaving the original untouched.
Practice exercises
- Easy:
Use
.map()to turn an array of strings into an array of their lengths. - Medium:
Use
.filter()and.map()together to get the names of all users older than 18 from an array of user objects. - Hard:
Reimplement
.map()yourself as a functionmyMap(array, callback)using a plainforloop.
Interview questions
What's the core difference between `map()` and `forEach()`?
map() collects the return value of the callback for every item into a brand-new array; forEach() calls the callback for its side effects and always returns undefined, so assigning its result to a variable is a common mistake.
What's the difference between `filter()` and `find()`?
filter() returns a new array containing every element that passes the test, even if that's zero or many; find() returns just the first matching element itself (not wrapped in an array), or undefined if nothing matches, and stops iterating as soon as it finds one.
How do `some()` and `every()` differ, and how do they short-circuit?
some() returns true as soon as any element passes the test (short-circuiting on the first success); every() returns false as soon as any element fails (short-circuiting on the first failure). If neither triggers early, some finishes as false and every finishes as true.
How does `reduce()` work?
It calls the callback once per element with an accumulator and the current element; whatever the callback returns becomes the accumulator passed into the next call, and after the last element, that final accumulator is reduce's own return value.
What does `[].reduce((sum, n) => sum + n);` do, and why?
It throws TypeError: Reduce of empty array with no initial value. With no initial value, reduce uses the array's first element as the starting accumulator and starts calling the callback from the second — on an empty array there's no first element to start from.
Why does providing an initial value to `reduce()` matter even on a non-empty array?
Without one, the first callback call is skipped and the first element is used as the starting accumulator instead — which silently changes both the type the accumulator starts as and how many times the callback actually runs, which can matter if the callback has side effects or expects a specific accumulator shape.
Which common array methods mutate the array in place, and which don't?
Mutating: push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin. Non-mutating, returning a new array or value: map, filter, slice, concat, reduce, find, some, every, includes.
What does this log? `const nums = [1, 2, 3, 4, 5]; console.log(nums.filter((n) => n % 2 === 0).map((n) => n * 10));`
[20, 40] — filter first narrows the array down to the even numbers ([2, 4]), and map then transforms just those into [20, 40]; each method in the chain works on the array the previous one returned.
What does this log, and why? `console.log(["1", "2", "3"].map(parseInt));`
[1, NaN, NaN]. map calls its callback with (element, index, array), and parseInt(string, radix) treats that second argument as a radix — so it actually runs parseInt("1", 0) (radix 0 defaults to base 10, giving 1), parseInt("2", 1) (radix 1 is invalid, giving NaN), and parseInt("3", 2) ("3" isn't a valid binary digit, giving NaN).
How would you fix the `array.map(parseInt)` bug?
Use array.map(Number) if there's no radix concern, or wrap it explicitly: array.map((str) => parseInt(str, 10)) — either avoids accidentally passing map's index argument through as parseInt's radix.
Why doesn't this same class of bug usually show up with `array.map(String)` or `array.map(Boolean)`?
Those functions only look at their first argument and ignore any extra ones map passes in (index, array) — parseInt is the classic trap specifically because its second parameter happens to have a meaning that changes the result.
What does this log? `const nums = [5, 1, 4, 2, 3]; nums.sort((a, b) => b - a); console.log(nums);`
[5, 4, 3, 2, 1] — a comparator returning a negative number means 'a comes first', positive means 'b comes first'; b - a puts larger numbers first, producing descending order, and sort mutates nums directly.
What is `reduceRight()`, and how does it differ from `reduce()`?
It runs the same accumulator pattern as reduce(), but processes the array from the last element to the first instead of first to last — useful when the order of combination matters, like right-associative operations.
What does this log? `const orders = [{ amount: 10 }, { amount: 25 }, { amount: 5 }]; const total = orders.filter((o) => o.amount > 8).map((o) => o.amount).reduce((sum, n) => sum + n, 0); console.log(total);`
35 — filter keeps the orders over 8 (10 and 25), map extracts just their amounts, and reduce sums them: 10 + 25.
Can you `break` or `continue` out of a `forEach()` loop early?
No — forEach always runs its callback for every element with no way to stop it partway through; a return inside the callback only skips the rest of that one call, it doesn't end the loop. Use a plain for/for...of loop (which supports break) if you need to exit early.
What does this log? `[1, 2, 3].forEach((n) => { if (n === 2) return; console.log(n); });`
1 then 3 — return inside the callback only ends that single invocation for n === 2, skipping its console.log; it does not break out of the overall forEach loop, which continues on to 3.
What does this log? `console.log([].every((x) => x > 0)); console.log([].some((x) => x > 0));`
true then false. every on an empty array is vacuously true — there's no element to fail the test — while some is false because there's no element to satisfy it either.
Why can't `.includes()` find an object by one of its property values?
includes() compares each element to the search value using SameValueZero equality — for objects, that means reference identity, not structural content. To find an object by a property value, you need find() with a predicate, e.g. arr.find((o) => o.id === 5).
Why should `map()` always return the same number of items as the input array?
That's the contract callers rely on — map is meant to transform, not filter. Returning undefined for some items to 'skip' them still leaves those slots in the output array (now holding undefined), which is a misuse; filter (possibly chained with map) is the correct tool for dropping items.
How would you implement your own version of `map()` using a plain loop, to understand what it's actually doing?
Create a new empty array, loop over the input with a for loop, call the callback with (element, index, array) on each pass, and push its return value into the new array — which is exactly what the built-in map does internally.
What's the difference between `Array.prototype.flat()` and `Array.prototype.flatMap()`?
flat(depth) just flattens an already-built nested array by the given depth; flatMap() runs a mapping callback and flattens the results by one level in the same pass, which is more efficient than calling .map() and then .flat() separately when the mapping itself produces nested arrays.
What is `Array.prototype.at()` useful for that bracket notation isn't?
arr.at(-1) accesses from the end using a negative index directly; bracket notation has no negative-index support (arr[-1] just looks up a nonexistent property named "-1" and returns undefined), so without .at() you'd need arr[arr.length - 1].
Why might chaining `.filter().map()` be less efficient than a single `.reduce()` on a very large array?
Each chained method is a separate full pass over the array, building and discarding an intermediate array in between; a single reduce() (or a hand-written loop) can filter and transform in one pass, avoiding the extra intermediate allocation — a trade worth making only when the array is genuinely large and this is a measured bottleneck.
Does `.sort()` guarantee a stable sort — that equal elements keep their relative order?
Yes, as of the modern ECMAScript specification, Array.prototype.sort() is required to be stable — elements that compare as equal retain their original relative order, which matters when sorting by one key while wanting a previous ordering by another key preserved.
How would you count how many times each value appears in an array using `reduce()`?
Build an object accumulator, incrementing a per-value counter on each pass: arr.reduce((tally, v) => { tally[v] = (tally[v] || 0) + 1; return tally; }, {}).
What's a scenario where `.some()` is a better choice than `.filter().length > 0`?
.some() stops as soon as it finds one match; .filter() always scans the whole array and builds a full result array just to check its length, doing unnecessary work when you only care whether at least one match exists.