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
| Parameter | Type | Default | Description |
|---|---|---|---|
callbackFn | Function | — | Function executed once per assigned index. Its return value is discarded. |
thisArg | any | undefined | Value passed as this to callbackFn. Ignored when callbackFn is an arrow function. |
The callback receives three arguments:
| Argument | Type | Description |
|---|---|---|
element | any | The current element being processed. |
index | number | Index of the current element. |
array | Array | The 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
| Situation | Better fit |
|---|---|
| I only want side effects (log, mutate, dispatch) | forEach() |
| I want a new transformed array | map() |
| I want a single matching element | find() |
| I want to know if any element matches | some() |
| I want to flatten nested arrays | flat() |
I need to break or continue mid-iteration | for...of loop |
I need sequential await between iterations | for...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
- Array.prototype.map(): returns a new array of transformed values
- Array.prototype.find(): returns the first match and short-circuits
- Array.prototype.flat(): flattens nested arrays without a hand-rolled loop