Sitemap

for...of vs forEach Which one is better?

5 min readOct 3, 2024

--

Press enter or click to view image in full size

When working with JavaScript, choosing the right looping construct can impact not only the readability and maintainability of your code but also its performance and memory usage. Two common ways to iterate over iterable objects like arrays are the for...of loop and the Array.prototype.forEach method. Let's delve into a comparison of these two constructs with a focus on memory efficiency.

for...of Loop

The for...of loop is a modern JavaScript construct introduced in ES6 (ECMAScript 2015) that allows iteration over iterable objects (e.g., arrays, strings, Maps, Sets).

Example:

const array = [1, 2, 3, 4, 5];

for (const item of array) {
console.log(item);
}

forEach Method

The forEach method is an array method that executes a provided callback function once for each array element.

Example:

const array = [1, 2, 3, 4, 5];

array.forEach((item) => {
console.log(item);
});

Memory Efficiency Comparison

**1. Callback Functions and Memory Overhead

forEach:

  • Callback Creation: Each time forEach is called, a new callback function is created (if using an anonymous function or an arrow function). This can lead to additional memory usage, especially if the loop is executed many times or within a frequently called function.
  • Closure Scope: If the callback function captures variables from its surrounding scope (closures), it may prevent those variables from being garbage collected as long as the callback exists.

for...of:

  • No Callback: The for...of loop does not require a callback function, eliminating the memory overhead associated with function creation.
  • Simpler Scope: Since there’s no additional function scope, there’s less risk of inadvertently retaining references to variables, aiding garbage collection.

**2. Internal Iteration vs External Iteration

forEach:

  • Internal Iteration: The iteration logic is handled internally by the forEach method. While this abstracts away the loop mechanics, it can sometimes lead to less optimized memory usage because the engine must handle the callback invocations.

for...of:

  • External Iteration: The loop control is explicit, giving the JavaScript engine more flexibility to optimize memory usage and loop execution.

**3. Control Flow Impact

forEach:

  • Limited Control: You cannot use break, continue, or return to control the loop flow within a forEach. This limitation can sometimes lead to less efficient loops if you're trying to terminate early or skip iterations based on conditions.

for...of:

  • Full Control: You can use break, continue, and return to manage the loop flow, which can lead to more efficient memory usage by avoiding unnecessary iterations.

Performance Benchmarks

While memory efficiency is a crucial aspect, it’s also worth noting that performance often correlates with memory usage. Various benchmarks have shown that:

  • for...of loops generally outperform forEach in both speed and memory usage, especially with large datasets.
  • forEach may introduce slight overhead due to callback function invocations, which can accumulate in memory usage over extensive iterations.

Example Benchmark:

const largeArray = Array.from({ length: 1e6 }, (_, i) => i);

// for...of
console.time('for...of');
for (const item of largeArray) {
// Perform operations
}
console.timeEnd('for...of');

// forEach
console.time('forEach');
largeArray.forEach((item) => {
// Perform operations
});
console.timeEnd('forEach');

Results may vary based on the JavaScript engine and environment, but typically for...of is faster and more memory-efficient.

Best Practices

Use for...of When:

  • You need better performance and memory efficiency, especially with large datasets.
  • You require control over the loop flow (break, continue).
  • You want to avoid the overhead of callback functions.

Use forEach When:

  • You prefer a functional programming style.
  • You don’t need to break or continue the loop.
  • The dataset is reasonably small, and performance/memory isn’t a critical concern.

for...of with Async/Await

When you use a for...of loop in conjunction with async/await, it allows you to write asynchronous code that behaves synchronously, meaning that each iteration can wait for the completion of an asynchronous operation before proceeding to the next iteration.

Example:

async function processItems(items) {
for (const item of items) {
await asyncOperation(item); // Waits for each async operation to complete
console.log(`Processed: ${item}`);
}
}

How It Works:

  • Sequential Execution: The await keyword pauses the execution of the loop until the asyncOperation(item) promise is resolved or rejected. Only after the promise is settled does the loop continue to the next iteration.
  • Error Handling: Errors thrown within the asyncOperation can be caught using try/catch blocks, allowing for more predictable error handling.

forEach with Promises

In contrast, when you use Array.prototype.forEach, it does not handle asynchronous operations in the same way. Each callback passed to forEach runs immediately without waiting for asynchronous operations to complete.

Example:

async function asyncOperation(item) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Processed: ${item}`);
resolve();
}, Math.random()*1000); // Simulate an async operation with a delay
});
}

function processItems(items) {
items.forEach(async item => {
await asyncOperation(item); // This works and returns a promise
// This runs when asyncOperation resolves
console.log(`Done processing: ${item}`);
});
}

// Example usage
const items = ['item1', 'item2', 'item3'];
processItems(items);

How It Works:

  • Non-Sequential Execution: All calls to asyncOperation(item) are initiated almost simultaneously without waiting for any of them to complete. This means that the operations do not execute in the order you might expect. They run concurrently, and the order of completion is not guaranteed.
  • Lack of Await: Because forEach does not support the await keyword, the function does not pause, leading to unexpected behavior if the order of processing matters or if there are dependencies between the operations.

Async Function:

  • When you declare a function as async, it means that the function always returns a promise. If you use await inside it, the execution will pause until the awaited promise is resolved or rejected.

Callback in forEach:

  • When you use async in a callback function for Array.prototype.forEach, each iteration of forEach calls this async function immediately, but forEach itself does not wait for these async functions to resolve before moving to the next iteration.

What Happens Internally with forEach

  • When you call forEach, it executes the callback for each item immediately:
  • Each time the callback is executed, it returns a promise (because it’s an async function).
  • However, forEach does not handle these promises. It does not know or care about the completion of the async operations inside the callback.

Implications of Asynchronous Behavior

Concurrent vs Sequential Execution:

  • for...of: Guarantees sequential processing. Each operation must complete before the next starts.
  • forEach: Executes all operations concurrently, which can lead to race conditions or unexpected outcomes, especially if the order of execution is important.

Conclusion

When considering memory efficiency, the for...of loop generally has an edge over the forEach method in JavaScript. This advantage stems from the absence of callback function overhead and the ability to control loop execution more precisely. However, the differences are often marginal and may not impact smaller applications or datasets significantly.

Recommendation: For scenarios where performance and memory usage are critical — such as processing large arrays or within performance-sensitive applications — for...of is typically the better choice. For more readable, concise code where performance is less of a concern, forEach remains a perfectly valid and widely used option.

Additional Considerations

Readability and Maintainability:

  • Choose the loop that best fits the coding standards and readability preferences of your team or project.

Asynchronous Operations:

  • If you need to perform asynchronous operations within your loop, for...of can be more straightforward to use with async/await compared to forEach.

--

--

Aditya Yadav
Aditya Yadav

Written by Aditya Yadav

Software Engineer who talks about tech concepts in web development https://www.linkedin.com/in/aditya-yadav-01/