Episode 1 — Fundamentals / 1.22 — Array Methods
1.22.d — forEach()
In one sentence:
forEach()executes a function once for every element but returnsundefined— it is designed purely for side effects, not for producing a new value.
Navigation: ← 1.22.c — reduce() · 1.22.e — some() and every() →
1. What forEach() does
forEach loops through every element and passes it to your callback. Unlike map or filter, it does not build a new array and does not return anything useful.
const fruits = ["apple", "banana", "cherry"];
fruits.forEach(fruit => {
console.log(fruit.toUpperCase());
});
// APPLE
// BANANA
// CHERRY
const result = fruits.forEach(f => f.toUpperCase());
console.log(result); // undefined ← always!
2. Syntax
arr.forEach(callback(element, index, array));
| Parameter | Description |
|---|---|
element | The current element |
index | (optional) The current index |
array | (optional) The original array |
| Return | Always undefined — no matter what the callback returns |
const colors = ["red", "green", "blue"];
colors.forEach((color, index, arr) => {
console.log(`${index + 1}/${arr.length}: ${color}`);
});
// 1/3: red
// 2/3: green
// 3/3: blue
3. Cannot break or continue
Unlike a for loop, there is no way to exit a forEach early:
// THIS DOES NOT WORK
[1, 2, 3, 4, 5].forEach(n => {
if (n === 3) break; // SyntaxError: Illegal break statement
console.log(n);
});
// THIS ALSO DOES NOT WORK (return only exits the current callback, not the loop)
[1, 2, 3, 4, 5].forEach(n => {
if (n === 3) return; // skips this iteration but does NOT stop the loop
console.log(n);
});
// 1, 2, 4, 5 ← 3 was skipped, but 4 and 5 still run
If you need early exit, use for...of, some(), or every().
4. Cannot use await properly inside forEach
This is a very common bug:
// BUG: forEach does not await each callback
const urls = ["/api/1", "/api/2", "/api/3"];
urls.forEach(async (url) => {
const res = await fetch(url); // fires all at once, not sequentially
const data = await res.json();
console.log(data);
});
console.log("Done!");
// "Done!" prints BEFORE any fetch completes!
Why: forEach does not look at the return value of the callback. It gets a Promise back and ignores it.
Fix: use for...of:
async function fetchAll(urls) {
for (const url of urls) {
const res = await fetch(url);
const data = await res.json();
console.log(data);
}
console.log("Done!"); // actually runs after all fetches
}
5. forEach vs for...of
| Feature | forEach | for...of |
|---|---|---|
| Break / continue | Not possible | Yes |
await support | No (fires and forgets) | Yes (sequential await) |
| Return value | undefined | N/A (it is a statement) |
| Readability | Callback-style | Loop-style |
| Works on | Arrays (and array-likes with Array.from) | Any iterable (arrays, strings, Maps, Sets) |
| Sparse arrays | Skips holes | Visits holes as undefined |
| Performance | Slight overhead (function call per element) | Marginally faster in tight loops |
When to prefer for...of:
- Need
break,continue, orreturn(from enclosing function) - Need
awaitinside the loop - Iterating non-array iterables (Map, Set, generators)
When to prefer forEach:
- Simple side-effect on every element, no early exit needed
- Stylistic preference for functional/callback style
6. forEach vs map — common mistake
// WRONG: using forEach to build an array
const doubled = [];
[1, 2, 3].forEach(n => doubled.push(n * 2));
// Works, but awkward — why manage the array yourself?
// RIGHT: use map
const doubled2 = [1, 2, 3].map(n => n * 2);
// WRONG: using map for a side effect (wasteful)
[1, 2, 3].map(n => console.log(n));
// Creates a useless array [undefined, undefined, undefined]
// RIGHT: use forEach for side effects
[1, 2, 3].forEach(n => console.log(n));
Decision rule:
| Situation | Use |
|---|---|
| Need the result array | map |
| Just performing an action per element | forEach |
| Need early exit | for...of |
7. Side-effect-oriented examples
Logging data
const users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "user" },
{ name: "Carol", role: "user" },
];
users.forEach(user => {
console.log(`[${user.role.toUpperCase()}] ${user.name}`);
});
// [ADMIN] Alice
// [USER] Bob
// [USER] Carol
DOM updates
const items = ["Home", "About", "Contact"];
const nav = document.querySelector("nav");
items.forEach(item => {
const link = document.createElement("a");
link.textContent = item;
link.href = `/${item.toLowerCase()}`;
nav.appendChild(link);
});
Accumulating into an external data structure
const scores = new Map();
["Alice:85", "Bob:92", "Carol:78"].forEach(entry => {
const [name, score] = entry.split(":");
scores.set(name, Number(score));
});
console.log(scores);
// Map(3) { "Alice" => 85, "Bob" => 92, "Carol" => 78 }
Sending analytics events
const events = [
{ type: "click", target: "button#submit" },
{ type: "scroll", depth: 50 },
{ type: "click", target: "a.nav-link" },
];
events.forEach(event => {
analytics.track(event.type, event);
});
8. Updating external state
forEach is appropriate when you need to modify something outside the callback:
let total = 0;
const prices = [19.99, 42.50, 8.99, 35.00];
prices.forEach(price => {
total += price;
});
console.log(total); // 106.48
// Works, but reduce is more idiomatic for this:
// const total = prices.reduce((sum, p) => sum + p, 0);
9. forEach on array-like objects
forEach is an array method. NodeLists from querySelectorAll also have forEach, but older array-likes (like arguments) do not:
// NodeList has forEach (modern browsers)
document.querySelectorAll("p").forEach(p => {
p.style.color = "blue";
});
// arguments does NOT have forEach — convert first
function logArgs() {
// arguments.forEach(...); // TypeError!
Array.from(arguments).forEach(arg => console.log(arg));
}
10. Edge cases
// Empty array — callback never runs
[].forEach(n => console.log(n));
// (no output)
// Sparse arrays — skips holes
const sparse = [1, , 3];
sparse.forEach(n => console.log(n));
// 1
// 3 ← index 1 (the hole) is skipped entirely
// Mutating array during forEach — behavior is defined but confusing
const arr = [1, 2, 3, 4, 5];
arr.forEach((val, i) => {
if (val === 3) arr.push(99);
console.log(val);
});
// 1, 2, 3, 4, 5 ← 99 is NOT visited (length was fixed at start)
11. When forEach is the right choice
Use forEach when:
- You are performing a side effect (logging, DOM update, API call)
- You do not need the return values
- You do not need to break early
- You do not need
awaitinside the loop
For everything else, there is usually a better method.
Key takeaways
forEach()runs a callback on each element and always returnsundefined.- You cannot
break,continue, or properlyawaitinsideforEach. - Use
forEachfor side effects (logging, DOM, external state). - Use
mapwhen you need a new array,reducewhen you need a single value. - Use
for...ofwhen you need early exit orawait. - Do not use
mappurely for side effects — it creates a wasted array.
Explain-It Challenge
Explain without notes:
- What does
forEachreturn? Always? - Why does
awaitinsideforEachnot work as expected? - A colleague wrote
const names = users.forEach(u => u.name). What is wrong and how do you fix it?
Navigation: ← 1.22.c — reduce() · 1.22.e — some() and every() →