JavaScript Array forEach Loop: Complete Tutorial
Master the JavaScript forEach() method for iterating arrays without creating new output. Covers syntax, callback parameters, real-world iteration patterns, comparison with for loops, limitations, and common mistakes.
The forEach() method executes a function once for every element in an array. Unlike map() or filter(), it does not return a new array. It simply runs your callback on each element and returns undefined. This makes forEach() the right tool when you need to perform side effects (logging, DOM updates, API calls) for every item without building a new collection.
What forEach() Does
forEach() visits every element in order and passes it to a callback function. There is no return value and no way to stop early:
const fruits = ["apple", "banana", "cherry", "date"];
fruits.forEach(function (fruit) {
console.log(fruit.toUpperCase());
});
// APPLE
// BANANA
// CHERRY
// DATESyntax
array.forEach(callback(element, index, array), thisArg)| Parameter | Description |
|---|---|
element | The current element being processed |
index | Index of the current element (optional) |
array | The original array (optional) |
thisArg | Value to use as this inside the callback (optional) |
Return Value
Always undefined. If you need a new array, use map(). If you need a single accumulated value, use reduce().
Basic Examples
Using All Three Callback Parameters
const colors = ["red", "green", "blue"];
colors.forEach((color, index, arr) => {
const isLast = index === arr.length - 1;
console.log(`${index + 1}/${arr.length}: ${color}${isLast ? " (last)" : ""}`);
});
// 1/3: red
// 2/3: green
// 3/3: blue (last)Logging Table Rows
const employees = [
{ name: "Alice", department: "Engineering", salary: 95000 },
{ name: "Bob", department: "Marketing", salary: 72000 },
{ name: "Carol", department: "Engineering", salary: 110000 },
];
employees.forEach(emp => {
console.log(`${emp.name.padEnd(10)} ${emp.department.padEnd(15)} $${emp.salary}`);
});
// Alice Engineering $95000
// Bob Marketing $72000
// Carol Engineering $110000Updating Object Properties in Place
const tasks = [
{ id: 1, title: "Write tests", completed: false },
{ id: 2, title: "Fix bug #42", completed: false },
{ id: 3, title: "Deploy v2", completed: false },
];
tasks.forEach(task => {
task.completed = true;
task.completedAt = new Date().toISOString();
});
console.log(tasks[0].completed); // true
console.log(tasks[0].completedAt); // "2026-03-05T..."Mutations Are Visible
Because objects are passed by reference, changes made inside forEach() affect the original array's objects. This is intentional when you need to update records in place, but it can be surprising if you expect immutability.
Real-World Patterns
Sending Batch Notifications
const subscribers = [
{ email: "alice@example.com", name: "Alice" },
{ email: "bob@example.com", name: "Bob" },
{ email: "carol@example.com", name: "Carol" },
];
subscribers.forEach(subscriber => {
sendEmail({
to: subscriber.email,
subject: "Weekly Digest",
body: `Hi ${subscriber.name}, here's your weekly update.`,
});
console.log(`Sent to ${subscriber.email}`);
});Populating a Map from Array Data
const rawProducts = [
{ sku: "KB-001", name: "Mechanical Keyboard", price: 129.99 },
{ sku: "MS-002", name: "Wireless Mouse", price: 49.99 },
{ sku: "HD-003", name: "USB-C Hub", price: 39.99 },
];
const productMap = new Map();
rawProducts.forEach(product => {
productMap.set(product.sku, product);
});
console.log(productMap.get("MS-002").name); // "Wireless Mouse"Accumulating Side Effects with Index
const steps = [
"Clone the repository",
"Install dependencies",
"Configure environment variables",
"Run database migrations",
"Start the development server",
];
const instructions = [];
steps.forEach((step, index) => {
instructions.push(`Step ${index + 1}: ${step}`);
});
console.log(instructions.join("\n"));forEach() Cannot Be Stopped Early
This is the most important limitation. You cannot use break, continue, or return to exit early from forEach():
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// return only exits the CURRENT callback invocation, not the loop
numbers.forEach(n => {
if (n > 5) return; // Does NOT stop forEach. Just skips this callback run.
console.log(n);
});
// 1, 2, 3, 4, 5 (logs these, but forEach still runs for 6-10)If you need to stop early, use a for loop with break, or use find() / some() which stop at the first match:
// for loop: stops at first match
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] > 5) break;
console.log(numbers[i]);
}
// some(): stops when callback returns true
numbers.some(n => {
if (n > 5) return true; // Stops iteration
console.log(n);
return false;
});No Break in forEach
Throwing an exception is the only way to actually halt forEach(), and that is an anti-pattern. If you need early termination, use a for loop, for...of loop, some(), every(), or find() instead.
forEach() vs for Loop
| Feature | forEach() | for loop |
|---|---|---|
| Early exit (break) | Not supported | Supported |
| Skip iteration (continue) | Use return (skips callback) | Use continue |
| Return value | undefined | N/A |
| Access to index | Via second callback parameter | Loop variable i |
| Async/await support | Does not wait for promises | Works with await in loop body |
| Readability | Higher for simple iteration | Better for complex control flow |
| Performance | Marginally slower (function call overhead) | Marginally faster |
Async Pitfall
forEach() fires all callbacks synchronously and does not await promises:
const urls = ["/api/user/1", "/api/user/2", "/api/user/3"];
// Bug: forEach does NOT await. All fetches fire at once, results are lost.
urls.forEach(async url => {
const response = await fetch(url);
const data = await response.json();
console.log(data); // Logs in unpredictable order
});
console.log("Done"); // Logs BEFORE any fetch completes
// Fix: use for...of for sequential async
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
console.log(data); // Sequential, predictable order
}
console.log("Done"); // Logs AFTER all fetchesforEach() Skips Empty Slots
Sparse arrays (created with delete or literal holes) have empty slots that forEach() skips entirely:
const sparse = [1, , 3, , 5]; // Holes at index 1 and 3
sparse.forEach((value, index) => {
console.log(`Index ${index}: ${value}`);
});
// Index 0: 1
// Index 2: 3
// Index 4: 5
// (indices 1 and 3 are skipped, not logged as undefined)This is different from undefined values, which ARE visited:
const withUndefined = [1, undefined, 3, undefined, 5];
withUndefined.forEach((value, index) => {
console.log(`Index ${index}: ${value}`);
});
// Index 0: 1
// Index 1: undefined
// Index 2: 3
// Index 3: undefined
// Index 4: 5Common Mistakes
Trying to return a value from forEach():
const numbers = [1, 2, 3, 4, 5];
// Bug: forEach always returns undefined
const doubled = numbers.forEach(n => n * 2);
console.log(doubled); // undefined
// Fix: use map() when you need a new array
const doubled2 = numbers.map(n => n * 2);
console.log(doubled2); // [2, 4, 6, 8, 10]Expecting break to work:
const items = ["a", "b", "c", "d"];
// SyntaxError: break is not valid inside a callback function
// items.forEach(item => {
// if (item === "c") break; // SyntaxError
// });
// Fix: use for...of
for (const item of items) {
if (item === "c") break;
console.log(item);
}Using forEach() for async operations:
const ids = [1, 2, 3];
// Bug: forEach does not await promises
ids.forEach(async id => {
const user = await fetchUser(id);
console.log(user.name);
});
// "All done" logs before any user is fetched
console.log("All done");
// Fix: use for...of with await
async function loadUsers() {
for (const id of ids) {
const user = await fetchUser(id);
console.log(user.name);
}
console.log("All done"); // Now truly done
}Best Practices
- Use forEach() only for side effects. If you need a new array, use map(). If you need a single value, use reduce().
- Choose for...of when you need early exit or async/await. forEach() cannot break or properly handle promises.
- Keep callbacks small. If the callback body exceeds 10 lines, extract it into a named function for readability.
- Do not chain after forEach(). Since it returns
undefined, any method chained after it (like.map()) will throw. - Use arrow functions for concise callbacks:
arr.forEach(item => console.log(item))is clean and readable.
Rune AI
Key Insights
- Side effects only: forEach() returns undefined; use map() or filter() when you need a new array
- No early exit: break and continue do not work; return only skips the current callback
- Async pitfall: forEach() does not await promises; use for...of for sequential async operations
- Skips empty slots: sparse array holes are ignored, but explicit undefined values are visited
- Keep it simple: extract long callbacks into named functions for clarity
Frequently Asked Questions
Does forEach() modify the original array?
Can I use break or continue inside forEach()?
What is the difference between forEach() and map()?
Does forEach() work on objects?
Is forEach() slower than a for loop?
Conclusion
The forEach() method is JavaScript's most straightforward tool for running a side-effect function on every element of an array. It excels at logging, DOM updates, batch mutations, and any operation where you process items without building a new collection. Its two critical limitations are the inability to exit early (no break) and the lack of proper async/await support. When either limitation applies, a for loop or for...of is the better choice, and when you need a new array, map() is the right fit.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.