jsguides

Array.prototype.forEach()

forEach(callbackFn, thisArg?)

forEach() executes a provided callback once for every element in the array, in ascending index order. It is the most direct way to iterate an array when you only need the side effects, and it is one of the first iteration methods most developers learn. The catch: it returns undefined, so it is not chainable, and the only way to stop iteration is to throw.

Syntax

forEach(callbackFn)
forEach(callbackFn, thisArg)

Parameters

ParameterTypeDefaultDescription
callbackFnFunctionFunction executed once per assigned index. Its return value is discarded.
thisArganyundefinedValue passed as this to callbackFn. Ignored when callbackFn is an arrow function.

The callback receives three arguments:

ArgumentTypeDescription
elementanyThe current element being processed.
indexnumberIndex of the current element.
arrayArrayThe array forEach() was called on. Useful inside method chains.

Return value

undefined. forEach() is not chainable. If you write [1, 2, 3].forEach(fn).filter(...), you get a TypeError on undefined. To get a transformed array back, use map().

Examples

Basic iteration

const items = ["apple", "banana", "cherry"];
items.forEach((item) => console.log(item));
// apple
// banana
// cherry

This is the typical side-effect pattern: log, push to another array, mutate a counter, send a network request per item. Because the return value is discarded, anything you want to keep has to land somewhere the callback closes over.

Accessing the index and the array

The second and third callback arguments give you the position and a reference to the original array. This is handy for numbering entries, building compound keys, or reading the length without capturing the variable outside.

const logEntry = (element, index, array) => {
  console.log(`a[${index}] = ${element} (length ${array.length})`);
};
[2, 5, , 9].forEach(logEntry);
// a[0] = 2 (length 4)
// a[1] = 5 (length 4)
// a[3] = 9 (length 4)  // index 2 skipped — sparse hole

Note that the length reported is the length at call time. Elements appended after forEach() starts are not visited, and indices deleted before being visited are skipped.

Using thisArg with a class method

The thisArg parameter passes a value to this inside the callback. It only works with regular function expressions; arrow functions ignore it because their this is lexically bound.

class Counter {
  constructor() { this.sum = 0; this.count = 0; }
  add(array) {
    array.forEach(function (entry) {
      this.sum += entry;
      this.count += 1;
    }, this); // pass the Counter instance as `this`
  }
}
const c = new Counter();
c.add([2, 5, 9]);
console.log(c.count, c.sum); // 3 16

If you prefer to keep using arrow functions, capture this outside the loop with const self = this and reference self inside the callback.

Chaining with filter()

The third callback argument lets you inspect the source array even after a chain of operations has produced a new one. The classic case: chain filter() to drop elements you do not want, then forEach() to act on what remains, using the array argument to see the post-filter length.

const numbers = [3, -1, 1, 4, 1, 5];
numbers
  .filter((n) => n > 0)
  .forEach((num, idx, arr) => {
    console.log(`positive #${idx} of ${arr.length}: ${num}`);
  });
// positive #0 of 4: 3
// positive #1 of 4: 1
// positive #2 of 4: 4
// positive #3 of 4: 1

Common Pitfalls

No way to break

There is no break or continue for forEach(). The only way to stop iteration is to throw, which is almost never what you want. If you need early exit, pick a method that short-circuits or a loop that supports break.

// ❌ forEach — scans every element even after a match
let found;
[1, 2, 3, 4].forEach((n) => { if (n === 3) found = n; });

// ✅ find() — stops at the first match
const found2 = [1, 2, 3, 4].find((n) => n === 3); // 3

// ✅ for...of with break — also stops at the first match
for (const n of [1, 2, 3, 4]) {
  if (n === 3) { found = n; break; }
}

For “did any element match?” use some(). For “did all elements match?” use every(). Both stop as soon as their answer is decided.

Return value is discarded

A common mistake is writing forEach as if it were map.

[1, 2, 3].forEach((n) => n * 2); // undefined
[1, 2, 3].map((n) => n * 2);    // [2, 4, 6]

If you wanted a new array of transformed values, use map(). If you wanted to accumulate one value, use reduce(). If you wanted a single matching element, use find(). Reach for forEach() when you genuinely only want side effects.

Async callbacks are not awaited

forEach does not await promises. An async callback returns a promise that is silently dropped, and iterations run concurrently rather than sequentially. The result is a classic race condition where the value you expected to be set is still the initial one.

const ratings = [5, 4, 5];
let sum = 0;
const sumFunction = async (a, b) => a + b;

// BUG: prints 0, not 14 — the awaits resolve after the log
ratings.forEach(async (rating) => { sum = await sumFunction(sum, rating); });
console.log(sum); // 0

For sequential async work, use a for...of loop, which respects await between iterations. The for...of loop pauses at each await expression, so the next iteration only starts after the previous promise resolves. That gives you deterministic, ordered execution that forEach cannot provide because it never knows whether a callback is async.

for (const rating of ratings) sum = await sumFunction(sum, rating);
console.log(sum); // 14

For parallel async work, use Promise.all(arr.map(asyncFn)) and then act on the resolved results.

When to use forEach

SituationBetter fit
I only want side effects (log, mutate, dispatch)forEach()
I want a new transformed arraymap()
I want a single matching elementfind()
I want to know if any element matchessome()
I want to flatten nested arraysflat()
I need to break or continue mid-iterationfor...of loop
I need sequential await between iterationsfor...of loop with await

forEach() is the right tool when iteration is the goal, the return value is irrelevant, and you never need to short-circuit. The moment you find yourself setting a flag inside a forEach to “remember” the first match, switch to find() or some().

See Also