JavaScript Array Iteration Methods

Array iteration methods are functions built into every JavaScript array that let you loop over its elements without writing a manual for loop. Instead of tracking an index yourself, you pass a callback function, and the array calls that function once for each element. This makes code shorter, less error-prone, and easier to read because the intent (“transform every item”, “keep only some items”, “combine everything into one value”) is stated directly by the method name you choose.

Overview: How Iteration Methods Work

Every iteration method follows the same basic contract: you call it on an array, you give it a callback function, and the method invokes that callback once for each element in order, passing three arguments to it: the current element, its index, and the full array being iterated. What differs between the methods is what they do with the callback’s return value and what they hand back to you.

forEach() ignores the callback’s return value entirely and always returns undefined — it exists purely for side effects like logging or pushing into another structure. map() collects every return value into a brand-new array of the same length. filter() treats the return value as a boolean test and keeps only the elements where it was truthy, producing a new (possibly shorter) array. reduce() is the most general: it carries an accumulator value from one call to the next, letting you fold the whole array down into a single result — a sum, an object, another array, anything. find() and findIndex() stop as soon as the callback returns true and give you the element (or its index); some() and every() also stop early, testing whether at least one or all elements satisfy a condition.

Internally, these methods are just wrappers around a loop. The JavaScript engine walks the array’s indices from 0 to length - 1, calling your callback at each step with this bound according to a second, optional argument you can pass to most of these methods. Two details matter a lot in practice: none of map, filter, find, some, or every mutate the original array — they read it and return something new (or nothing) — and all of them skip empty slots in sparse arrays (arrays with holes), which is a subtle difference from a plain for loop.

Syntax

array.forEach((element, index, array) => { /* side effect */ });
const mapped   = array.map((element, index, array) => newValue);
const filtered = array.filter((element, index, array) => condition);
const result   = array.reduce((accumulator, element, index, array) => newAccumulator, initialValue);
const found    = array.find((element, index, array) => condition);
const foundIdx = array.findIndex((element, index, array) => condition);
const any      = array.some((element, index, array) => condition);
const all      = array.every((element, index, array) => condition);
const flat     = array.flatMap((element, index, array) => newValueOrArray);
Method Returns Stops early? Typical use
forEach undefined No Side effects (logging, DOM updates)
map New array, same length No Transform each element
filter New array, same or shorter No Keep matching elements
reduce Any single value No Sums, grouping, building objects
find Element or undefined Yes Get the first match
findIndex Index or -1 Yes Get the position of the first match
some Boolean Yes “Does at least one match?”
every Boolean Yes “Do all match?”
flatMap New, flattened array No Map then flatten one level

Examples

Example 1: forEach vs. map

const numbers = [1, 2, 3, 4, 5];

let sumForEach = 0;
numbers.forEach(n => {
  sumForEach += n;
});
console.log(sumForEach);

const doubled = numbers.map(n => n * 2);
console.log(doubled);

Output:

15
[ 2, 4, 6, 8, 10 ]

forEach is used here purely to accumulate a side effect (adding to sumForEach); its own return value is thrown away, which is why it can’t be chained. map, in contrast, produces a brand-new array — the original numbers array is untouched — because its whole purpose is to hand back a transformed value for every element.

Example 2: Filtering and reducing a shopping cart

const cart = [
  { name: 'Keyboard', price: 45, inStock: true },
  { name: 'Monitor', price: 199, inStock: false },
  { name: 'Mouse', price: 25, inStock: true },
  { name: 'Webcam', price: 60, inStock: true },
];

const availableItems = cart.filter(item => item.inStock);
console.log(availableItems.map(item => item.name));

const total = availableItems.reduce((sum, item) => sum + item.price, 0);
console.log(`Total: $${total}`);

Output:

[ 'Keyboard', 'Mouse', 'Webcam' ]
Total: $130

This is a realistic chain: filter narrows the array down to only the items that are in stock, then reduce folds that shorter array into a single total by carrying a running sum starting from the initial value 0. Chaining filter and reduce like this is extremely common — filter first to shrink the working set, then reduce to combine it.

Example 3: Searching and testing with find, some, every, and flatMap

const users = [
  { id: 1, name: 'Ana', age: 17 },
  { id: 2, name: 'Ben', age: 22 },
  { id: 3, name: 'Cleo', age: 19 },
];

const adult = users.find(u => u.age >= 18);
console.log(adult);

const index = users.findIndex(u => u.name === 'Cleo');
console.log(index);

console.log(users.some(u => u.age < 18));
console.log(users.every(u => u.age >= 18));

const tags = [['js', 'web'], ['css'], ['html', 'dom']];
console.log(tags.flatMap(t => t));

Output:

{ id: 2, name: 'Ben', age: 22 }
2
true
false
[ 'js', 'web', 'css', 'html', 'dom' ]

find stops at the very first user whose age is 18 or older and returns that whole object — not just true. findIndex does the same search but returns a position instead. some confirms at least one user is a minor, while every confirms not all users are adults. flatMap maps each nested array to itself and then flattens the results by one level, which is exactly why it’s the idiomatic tool for “map, then flatten” instead of calling .map().flat() separately.

Under the Hood: How reduce() Actually Works

It helps to see that reduce is not magic — it’s a small loop that carries a value forward. Here is a simplified reimplementation that behaves the same way as the built-in version:

function myReduce(array, callback, initialValue) {
  let accumulator = initialValue;
  let startIndex = 0;

  if (accumulator === undefined) {
    accumulator = array[0];
    startIndex = 1;
  }

  for (let i = startIndex; i < array.length; i++) {
    accumulator = callback(accumulator, array[i], i, array);
  }

  return accumulator;
}

const result = myReduce([1, 2, 3, 4], (acc, n) => acc + n, 0);
console.log(result);

Output:

10

On each pass through the loop, the engine calls your callback with whatever the previous callback call returned, which is why it’s called an “accumulator” — it accumulates state across iterations. If you don’t supply an initial value, the real reduce uses the first element as the starting accumulator and begins looping from index 1 instead of 0, exactly as shown above. Every other iteration method (map, filter, forEach, and so on) can actually be written in terms of a loop just like this one — they only differ in what they do with the callback’s return value and what they hand back at the end.

Common Mistakes

Mistake 1: Forgetting to return from a map() callback with braces

An arrow function with a block body (curly braces) does not implicitly return anything — you must write return explicitly. Forgetting it is one of the most common bugs with map:

const doubled = [1, 2, 3].map(n => { n * 2 });
console.log(doubled);

Output:

[ undefined, undefined, undefined ]

The code is perfectly valid JavaScript — { n * 2 } is parsed as a block statement, not an object, and since nothing is returned from that block, every call yields undefined. Fix it by either adding return or dropping the braces entirely for an implicit return:

const doubled = [1, 2, 3].map(n => n * 2);
console.log(doubled);

Output:

[ 2, 4, 6 ]

Mistake 2: Calling reduce() on an empty array with no initial value

If you omit the initial value and the array is empty, there is no first element to use as a starting accumulator, and reduce throws instead of silently returning something like 0 or undefined:

try {
  const total = [].reduce((acc, n) => acc + n);
  console.log(total);
} catch (error) {
  console.log(error.message);
}

Output:

Reduce of empty array with no initial value

The fix is simple and should be a habit: always pass an explicit initial value (like 0 for sums or []/{} for building collections) so reduce has something safe to fall back on even when the array turns out to be empty.

Best Practices

  • Reach for the most specific method available: use find instead of filter(...)[0], and some/every instead of manually reducing to a boolean — they read clearer and stop iterating as soon as the answer is known.
  • Always pass an initial value to reduce, even when you’re fairly sure the array won’t be empty — it protects against a runtime crash and makes the accumulator’s type explicit at a glance.
  • Don’t use map when you only want side effects and don’t need the returned array — use forEach so the intent (“I’m not building anything new here”) is clear to readers.
  • Avoid mutating the array or outside state inside a map/filter callback; keep these pure so the result is predictable and the original array stays untouched.
  • Chain iteration methods (filter then map then reduce) instead of writing one large manual loop — each method documents one step of the transformation.
  • Prefer flatMap over a separate .map().flat() call when mapping each element to zero, one, or several values — it’s both clearer and slightly more efficient since it avoids building an intermediate nested array.
  • Remember that forEach cannot be stopped early (no break) and always returns undefined — if you need early exit, use a for...of loop or a method like some/find that naturally short-circuits.

Practice Exercises

  1. Given const prices = [12, 45, 8, 99, 23];, use filter to get all prices over 20, then use reduce to find their total.
  2. Given an array of student objects like { name: 'Sam', score: 72 }, use map to produce a new array of just the names of students who scored 60 or above (you’ll need to combine filter and map).
  3. Write a function hasDuplicate(array) that uses some together with indexOf or findIndex to return true if any value appears more than once in the array, and false otherwise.

Summary

  • Iteration methods call a callback once per element, passing (element, index, array), so you rarely need to manage an index manually.
  • forEach is for side effects and always returns undefined; map transforms every element into a new array of the same length.
  • filter keeps elements that pass a test; reduce folds the whole array into one accumulated value, and should always get an initial value.
  • find/findIndex and some/every all stop iterating as soon as the answer is determined, unlike map, filter, and forEach.
  • flatMap maps and flattens one level in a single pass, avoiding a separate .flat() call.
  • None of these methods (except when you explicitly mutate inside the callback) change the original array — they read it and return something new.