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.

JavaScriptbeginner
13 min read

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:

javascriptjavascript
const fruits = ["apple", "banana", "cherry", "date"];
 
fruits.forEach(function (fruit) {
  console.log(fruit.toUpperCase());
});
// APPLE
// BANANA
// CHERRY
// DATE

Syntax

javascriptjavascript
array.forEach(callback(element, index, array), thisArg)
ParameterDescription
elementThe current element being processed
indexIndex of the current element (optional)
arrayThe original array (optional)
thisArgValue 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

javascriptjavascript
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

javascriptjavascript
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     $110000

Updating Object Properties in Place

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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

javascriptjavascript
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():

javascriptjavascript
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:

javascriptjavascript
// 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

FeatureforEach()for loop
Early exit (break)Not supportedSupported
Skip iteration (continue)Use return (skips callback)Use continue
Return valueundefinedN/A
Access to indexVia second callback parameterLoop variable i
Async/await supportDoes not wait for promisesWorks with await in loop body
ReadabilityHigher for simple iterationBetter for complex control flow
PerformanceMarginally slower (function call overhead)Marginally faster

Async Pitfall

forEach() fires all callbacks synchronously and does not await promises:

javascriptjavascript
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 fetches

forEach() Skips Empty Slots

Sparse arrays (created with delete or literal holes) have empty slots that forEach() skips entirely:

javascriptjavascript
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:

javascriptjavascript
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: 5

Common Mistakes

Trying to return a value from forEach():

javascriptjavascript
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:

javascriptjavascript
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:

javascriptjavascript
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

  1. Use forEach() only for side effects. If you need a new array, use map(). If you need a single value, use reduce().
  2. Choose for...of when you need early exit or async/await. forEach() cannot break or properly handle promises.
  3. Keep callbacks small. If the callback body exceeds 10 lines, extract it into a named function for readability.
  4. Do not chain after forEach(). Since it returns undefined, any method chained after it (like .map()) will throw.
  5. Use arrow functions for concise callbacks: arr.forEach(item => console.log(item)) is clean and readable.
Rune AI

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
RunePowered by Rune AI

Frequently Asked Questions

Does forEach() modify the original array?

forEach() itself does not modify the array. However, your callback can modify objects within the array because objects are passed by reference. If you reassign the element parameter inside the callback, it has no effect on the original array. Modifying properties of objects, on the other hand, changes them everywhere.

Can I use break or continue inside forEach()?

No. `break` and `continue` are loop statements and cause a SyntaxError inside a callback function. You can use `return` to skip the rest of the current iteration (similar to `continue`), but there is no way to stop forEach() early. Use a for loop or for...of when you need break/continue behavior.

What is the difference between forEach() and map()?

forEach() runs a callback on every element and returns `undefined`. map() runs a callback on every element and returns a new array with the results. Use forEach() for side effects (logging, mutations, API calls). Use map() when you need to transform data into a new array.

Does forEach() work on objects?

forEach() is an array method. Plain objects do not have it. To iterate over an object's keys, use `Object.keys(obj).forEach()`, `Object.entries(obj).forEach()`, or a `for...in` loop.

Is forEach() slower than a for loop?

In practice, the difference is negligible for most applications. forEach() has a marginal overhead from calling a function per element, but modern JavaScript engines optimize this heavily. Choose based on readability and requirements (early exit, async), not micro-performance.

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.