forEach()

ES5+

Executes a provided function once for each array element.

Syntax

array.forEach(callback(element, index, array), thisArg)

Parameters

callback Function

Function to execute on each element

Return Value

undefined

undefined - forEach does not return a value

Examples

JavaScript
const fruits = ['apple', 'banana', 'cherry'];
fruits.forEach((fruit, index) => {
  console.log(index + ': ' + fruit);
});
Output:
// 0: apple 1: banana 2: cherry

📌 When to Use

Use forEach() when the goal is to do something with each element rather than to compute something from the elements: attaching event listeners, writing rows to a log, sending analytics events, or updating DOM nodes. It signals to readers that the loop exists purely for its side effects, since forEach() always returns undefined and cannot feed a chain. That signal is also the decision boundary: if you catch yourself declaring an empty array before the loop and pushing into it from the callback, you wanted map() or filter(); if you accumulate a running total in an outer variable, reduce() states that intent without shared mutable state. Two structural limits push you toward for...of instead: forEach() offers no way to break out early (no break, and return only skips to the next element), and it does not pause for asynchronous work - an async callback fires all its promises immediately rather than awaiting them in sequence. So choose forEach() for short, synchronous, fire-for-every-element operations, and switch to for...of the moment you need early exit, await, or interaction with surrounding control flow such as continue or labeled loops.

⚠️ Common Mistakes

Trying to stop forEach() early. break is a syntax error inside the callback (it is a function, not a loop body), and return merely skips to the next element like continue. Code that "returns" on a found match keeps scanning the whole array anyway. Use for...of with break, or express the search with find() or some(), both of which short-circuit.

Building a result array manually: declaring const out = [] and calling out.push(...) inside forEach(). It works, but it splits one logical transformation across three statements and reintroduces the mutable temporary variable that map() exists to eliminate. The same applies to accumulating totals - that is reduce() wearing a disguise.

Expecting forEach() to await async callbacks. arr.forEach(async x => await save(x)) fires every save() concurrently and returns immediately - the promises are created and discarded, so code after the loop runs before any of them settle, and rejections become unhandled. Use for...of with await for sequential work or Promise.all(arr.map(...)) for parallel work.

Assuming every index gets visited on sparse arrays. Like map() and filter(), forEach() skips holes - the callback simply never runs for them - so an array created with new Array(5) logs nothing at all. If you need to touch every index including empty ones, use a classic for loop or fill the array first.

Mutating the array being iterated. The spec fixes the range up front but reads elements live: items appended during iteration are never visited, and removing the current element shifts its successor into the current index so it gets skipped. Iterate over a copy, or collect changes and apply them after the loop.

✅ Best Practices

For asynchronous per-element work, replace forEach() with for...of plus await when operations must run one at a time (rate-limited APIs, ordered writes), or with await Promise.all(items.map(fn)) when they can safely run in parallel. Both keep errors catchable with an ordinary try/catch.

Let the method name carry meaning: when a reviewer sees map(), filter(), or reduce() they immediately know a result is being produced, whereas forEach() promises "side effects only". Honoring that convention consistently makes unfamiliar code skimmable - violating it (a forEach that secretly builds state) costs every future reader a double-take.

Keep each forEach() callback focused on one side effect. If the body grows branches and nested conditions, extract it into a named function like syncRowToServer so the loop reads as a single sentence and the effect can be tested in isolation.

Use the second callback parameter when you need positions: list.forEach((item, i) => ...) is cleaner than maintaining a manual counter variable outside the loop, and it avoids the off-by-one drift that hand-managed counters invite during refactors.

Remember that forEach() works directly on NodeList from querySelectorAll() in all modern browsers, so converting to a real array first is only necessary when you also need map(), filter(), or other Array-only methods.

⚡ Performance Notes

forEach() pays one function invocation per element, and unlike a plain for loop the callback cannot be fully optimized away; historically that made it several times slower than an index-based loop in microbenchmarks, though modern V8 inlines monomorphic callbacks well enough that the gap has narrowed to the point of irrelevance for most workloads. Where the difference genuinely matters - tight numeric kernels, per-frame game or animation loops, processing millions of elements - a classic for loop with a cached length remains the fastest option and also permits early exit, which forEach() structurally cannot do (some() is the escape hatch if you must stay functional). forEach() allocates nothing itself, so memory pressure is never the concern; per-call overhead is. Also note that each element skipped as a sparse-array hole still costs a presence check, so keep arrays packed for best iteration speed across all methods.

🌍 Real World Example

Updating DOM Elements

Wiring up a batch of DOM elements is forEach() in its natural habitat: the work is inherently a side effect (mutating elements), there is no result to collect, and every element needs identical treatment. The index parameter doubles as a cheap way to tag each button with its position via a data attribute, which the click handler can later read back. querySelectorAll() returns a NodeList, and NodeList.prototype.forEach exists in all modern browsers, so no Array.from() conversion is needed for this pattern - it would only be required if you wanted to map or filter the elements afterwards.

const buttons = document.querySelectorAll('.action-btn');

buttons.forEach((button, index) => {
  button.addEventListener('click', handleClick);
  button.dataset.index = index;
  button.classList.add('initialized');
});
// Each button now has a click handler and index data

Related Methods