Use `for…of` instead of `.forEach(…)` typescript:S7728

"Having a look at the rule typescript:S7728 and after reading the article: ‘for…of vs forEach Which one is better?’ | by Aditya Yadav | Medium, I tested the proposed 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');

Executing the benchmark on my computer, forEach is always faster regardless of the number of iterations.

I tested on Chrome, Firefox, and Node on a Linux Ubuntu computer.

So I’m wondering whether this rule is still valid? Am I the only one to have this result?

Hello @Fred_D,

this is a very valid point. We are aware that .forEach may outperform for..of, but the same could be said about a classic for loop, which is usually the most performant. In scenarios where performance is a key aspect, I agree this rule can be disabled.

But for most cases, the advantages of a for...of outweigh the minor (in majority of situations) performance differences, especially when you need control flow (break/continue), async/await support, or want to iterate over non-array iterables like Maps and Sets.

Cheers!

So maybe the explanation of the issue: “The forEach method creates a function call for each array element, which introduces performance overhead compared to native for...of loops. This overhead becomes more significant with larger arrays,” should be mitigated.

1 Like

You’re right. I removed any mention of performance degradation in the rule description: Remove performance as a downside of forEach by vdiez · Pull Request #5851 · SonarSource/rspec · GitHub

1 Like

The answer is indeed that it’s complicated. Simple benchmarks may go either way. I experimented some more with Node 20.

A for…of loop is defined in ECMAScript in a complicated way involving returning a new object with value and done fields for every item. This probably explains why it runs slower when not optimized. When running the loop a few times (3 in my experiments), it speeds up greatly, but it is still somewhat slower than an indexed for loop.

The problems of forEach with closures and closed-over variables are mentioned in the linked post. Indeed, in a more realistic scenario where the inner function closes over a variable (I’m summing the numbers in the array), forEach stays slow, especially if it mutates the variable. The first run is a bit faster than for…of.

If for…of needs to be rewritten to an older ECMAScript version, the effect depends on whether this supports any iterable (e.g. TypeScript "downlevelIteration":true) or just array-like objects via index (e.g. TypeScript "downlevelIteration":false. If it supports any iterable, it’s probably always slow and it cannot be optimized well. If it works via index, it’s probably always fast.

1 Like