JavaScript's for-of loops are actually fast (V8)
Published on 01/01/2026
Updated on 14/01/2026
When it comes to iterating over arrays, for...of loops are believed to be significantly slower than traditional indexed loops. This has some grounding, since for-of loops are based on the iteration protocol, where the iterator has next() method returning an object like this: { value: /* element value */, done: /* a boolean, indicating the end of iteration */ }. Obviously, returning such object for every element will add an overhead.
However, I decided to test this since JS engines are constantly improving and sometimes can do amazing optimizations.
Table of contents
The benchmark
I decided to do the benchmarks with jsbenchmark.com on Chrome 143, Windows 11 and AMD Ryzen 5000U. All the benchmark tests are run sequentially.
The core idea of the benchmark
The idea is to create 5 types of arrays: integers, floats, strings, objects and mixed values.
- The benchmarks will be done with 3 array sizes: 5000, 50000, and 500000.
- There will be 6 types of loops:
-
Classic for with i++:
for (let i = 0; i < arr.length; i++) { DATA.doSomethingWithValue(arr[i]); } -
Classic for with i++ and cached array length:
const length = arr.length; for (let i = 0; i < length; i++) { DATA.doSomethingWithValue(arr[i]); } -
Classic for with i-- reversed:
for (let i = arr.length - 1; i >= 0; i--) { DATA.doSomethingWithValue(arr[i]); } -
for-of:
for (const x of arr) { DATA.doSomethingWithValue(x); } -
for-in:
for (const key in arr) { DATA.doSomethingWithValue(arr[key]); } -
forEach:
arr.forEach(x => DATA.doSomethingWithValue(x));
-
All the loops call a dummy function DATA.doSomethingWithValue() for each array element to make sure V8 doesn't optimize out something too much.
The setup
The setup will generate 5 types of arrays: random integers, random floats, random strings, objects and mixed values. It will also create a dummy function doSomethingWithValue (mentioned above).
const N = 5000 ; // Can be 50000 and 500000 as well const MAX_VALUE = 1000 ; const integers = Array . from ({ length : N }, () => Math . floor ( Math . random () * MAX_VALUE )); const floats = Array . from ({ length : N }, () => Math . random () * MAX_VALUE ); const strings = Array . from ({ length : N }, () => Math . random (). toString (). replace ( "." , "" )); const objects = Array . from ({ length : N }, () => ({ int : Math . floor ( Math . random () * MAX_VALUE ), float : Math . random () * MAX_VALUE , string : Math . random (). toString (). replace ( "." , "" ), })); const mixedValues = Array . from ({ length : N }, () => { switch ( Math . floor ( Math . random () * 4 )) { case 0 : return Math . floor ( Math . random () * MAX_VALUE ); case 1 : return Math . random () * MAX_VALUE ; case 2 : return Math . random (). toString (). replace ( "." , "" ); case 3 : return { int : Math . floor ( Math . random () * MAX_VALUE ), float : Math . random () * MAX_VALUE , string : Math . random (). toString (). replace ( "." , "" ), }; } }); return { integers, floats, strings, objects, mixedValues, doSomethingWithValue () { // A dummy function to make sure V8 doesn't optimize out something too much }, }
N is the size of the arrays and can be 5000, 50000, 500000.
The results

Benchmark results for N = 5000
The best 2 are classic i++ cached length and for-of (a bit surprising). Almost the same performance. Classic i++ and forEach are slightly behind. Looks like the reverse traversal in classic i-- reversed is doing more harm than good, probably has something to do with the CPU cache or V8 engine. The worst is for-in, which is not that surprising as it also does an additional access from the array.

Benchmark results for N = 50000
Almost the same pattern here, except forEach is becoming proportionally slower, closer to classic i-- reversed. for-in is becoming relatively worse than other loops.

Benchmark results for N = 500000
This one is a bit interesting. for-of is now less optimal for 500000 items (especially for floats). This is probably because for-of is harder to optimize, and since it was long running and too few runs were performed, V8 didn't optimize it. The same with forEach.
I decided to run a separate benchmark for classic i++ cached length, for-of and forEach, by repeating the loops 200 times, in order to warm the code up:

Benchmark results for N = 500000 - 200 repeats
Now, for-of is closer to classic i++ cached length. Still slower, the bad results during the warmup phase might also drag the average down. Overall, the warmup helps a lot. However, forEach doesn't get any significant benefit from this.
Still not fully satisfied, I did a benchmark once more with 1500 repeats for more warmup, but this time on jsben.ch. This is because jsbenchmark.com has timeout issues with long running code.

Benchmark results for N = 500000 - 1500 repeats
Finally, for-of is on par with classic i++ cached length. forEach also got faster, but still the slowest of 3.
Conclusion
In the recent years V8 has improved significantly in optimizing loops, including for-of loops. If your code is performance sensitive, for-of can be potentially as fast as classic loops. But as we see, for huge arrays the optimization is trickier and less reliable. You can still use it though if benchmarks show good performance. The most consistent winner is classic i++ cached array length, since JS engines are good at recognizing and optimizing such loops. For non performance sensitive code for-of still usually provides better ergonomics, so usually it should be preferred.
Happy New Year!