Compare for Loop and forEach Function in JavaScript
September 01, 2019
Introduction
I found an interesting discussion in a pull request for rxjs project.
The PR author would like to refactor this part of code
if (_infiniteTimeWindow) {
for (let i = 0; i < len && !subscriber.closed; i++) {
subscriber.next(<T>_events[i]);
}
} else {
for (let i = 0; i < len && !subscriber.closed; i++) {
subscriber.next((<ReplayEvent<T>>_events[i]).value);
}
}
into the following format
if (this._infiniteTimeWindow) {
_events.forEach(event => {
if (!subscriber.closed) {
subscriber.next(<T>event);
}
});
} else {
_events.forEach(event => {
if (!subscriber.closed) {
subscriber.next((<ReplayEvent<T>>event).value);
}
});
}
So basically, the author would like to refactor the for
loop by using forEach()
in array utility functions.
As we know, JavaScript / TypeScript has a set of powerful array utility functions including forEach()
, map()
, reduce()
and so on. Those utility functions greatly improve our productivity in array operations by using functional programming paradigm and they are clearer to read and easier to understand.
I believe the above refactored codes also intend to achieve a better readability and maintainability, but the member of rxjs library rejects the changes and claim that it is
removing optimizations that we had done on purpose
Is that really true? To prove that using a for
loop is more efficient than forEach()
, I did the following experiment.
Experiment: Comparison with Simple Value Addition and Assignment
The following codes are for comparison between for
loop and forEach()
using an array of 20 consisting of millions items simple value addition and assignment where the execution times are printed.
const list = Array(20000000).fill(0);
const list1 = [];
const list2 = [];
// 20 millions records with a for loop
console.time("for loop");
for (let i = 0; i < list.length; i++) {
list1[i] = i + 1;
}
console.timeEnd("for loop");
// 20 millions records with forEach()
console.time("forEach()");
list.forEach((_, i) => {
list2[i] = i + 1;
});
console.timeEnd("forEach()");
// Sanity check if the list1 and list2 are with the same calculation results
console.log(
list1.every((item, index) => item === list2[index]) &&
list1.length === list2.length
);
I run the codes for 5 times and get the following results.
for | forEach() | |
---|---|---|
1 | 408.801ms | 629.787ms |
2 | 410.799ms | 629.338ms |
3 | 409.691ms | 631.505ms |
4 | 390.361ms | 612.205ms |
5 | 390.503ms | 618.609ms |
So the result is very clear that for
loop performs better than forEach()
. Although there are 20 million records, the operations are simple, only value addition and assignment. What if the operations are complexer such as demonstrated in rxjs, every subscriber is notified and certain following actions are triggered? It will become a big difference. So, when you are working on a library, it’s critical to think about the performance, even if for
loop looks “more ugly” than forEach()
Besides the performance, what are the other differences?
Break Loops
If you want to exit the loop based on certain conditions, can forEach()
do that? The answer is yes, but it’s more troublesome and less straight forward compared to for
.
In for
loop, it’s easy to break the loop by
const list = [1, 2, 3, 4, 5];
for (let i = 0; i < list.length; i++) {
if (list[i] > 3) {
break;
}
console.log(list[i]);
}
// Only 1, 2, 3 are printed.
However, in forEach()
, you cannot directly use break
as SyntaxError: Illegal break statement
is thrown. It’s because break
keyword can only be used in a for
loop, while a forEach()
accepts a function as a parameter and break
keyword is NOT allowed in a function.
const list = [1, 2, 3, 4, 5];
list.forEach(item => {
if (item > 3) {
break;
}
console.log(item);
});
// SyntaxError: Illegal break statement
You can still use a hack way to achieve the break of loop, by using a try-catch block (inspired by HERE).
const list = [1, 2, 3, 4, 5];
try {
list.forEach(item => {
if (item > 3) {
throw Error();
}
console.log(item);
});
} catch (e) {}
// Only 1, 2, 3 are printed
Or another hack way is using some()
, because once the condition in the callback function is fulfilled, it will stop checking the rest of the items
const list = [1, 2, 3, 4, 5];
list.some(item => {
console.log(item);
return item > 2; // 3 is changed to 2 because the condition checking happens after the console.log()
});
// Only 1, 2, 3 are printed
However, neither of the ways looks pretty as they are not straight forward nor intuitive.
- try-catch block is meant for catching errors, but there is actually no error at all.
some()
is conceptually not meant for breaking a loop, but it just happens to have the same functionality ofbreak
.
Therefore, it takes extra time
for developers to figure out the outcome of these pieces of codes, which is also likely to cause careless mistakes and confusions.
Conclusion
In this article, we have discussed the differences of for
loop and forEach()
.
To summarize, I would recommend to use for
loop when
- You have to break in the loop
- You have a strict requirement on performance
If you don’t have the above 2 conditions, it’s actually better to use forEach()
because it looks clear and concise and is better to comprehend.
Featured image is credited to Thor Alvis on Unsplash