Simple Loops in JavaScript

I let SonarQube inspect my JavaScript code and it had opinions about my loops. I learnt about the for-of-loop. Let us see what we have.

Below four loop-constructions are for most practical purposes the same.

  // good old for-loop
  for ( let i=0 ; i<array.length ; i++ ) {
    const x = array[i];
    ...
  }

  // for-in-loop
  for ( const i in array ) {
    const x = array[i];
    ...
  }

  // for-of-loop
  for ( const x of array ) {
    ...
  }

  // forEach
  array.forEach((x) => {
     ...
  });

Well, if they are all practially the same, why bother? Why not pick one for all cases? Well, in the details, they are different when it comes to

  • simplicity to write / verboseness
  • performance
  • flexibility and explicitness

Lets discuss the loops.

The good old for-loop

The good old for-loop requires you to write the name of the array twice, and you need to explicitely increment the loop variable and compare it the length of the array. This is very easy, but it is possible to make silly mistakes.

In many/most cases it is unnecessarily explicit and verbose. However, as soon as you want to do things like:

  • skip first, or any other element
  • access several items in the array each (most commonly adjacent items: 01, 12, 23, 34, 45)
  • break / continue
  • modify the array – even the length of it – during the loop
  • sparse arrays, with undefined, it is obvious what you get

this becomes very natural with the good old loop. Doing it with the others will make it appear a bit contrived or the result may not be so obviously correct.

There is also something very explicit about the order. It may be true (or not?) that every implementation of JavaScript will always execute the other three loops in order. But you need to know that, to be absolutely sure, when reading the code. Not so with the good old for-loop. If order is a critical part of the algorithm and you may want to be explicit about it.

This is also the fastest loop.

The for-in-loop

for-in enumerates properties and loops over them. Do not use it for arrays:

  • it makes more sense to use for-in for Object, so the reader of the code may think your array is an object
  • are you 100% sure your array has no other enumerable properties, ever?
  • performance – this is by far the slowest loop
  • it is quite verbose

The for-of-loop

The for-of-loop is a bit “newer” and may not work in old browsers or JavaScript engines. That can be a reason to avoid it, but even more a reason why you do not see it in code you read.

I would argue this is the most practical, clean and simple loop, that should be used in most cases.

It is slightly slower than the good old for-loop, but faster than the other alternatives.

Array.forEach

I have been ranting about functional style code elsewhere. forEach is kind of an antipattern, because it is a functional construction that does nothing without a side-effect. A functional way to do something non-functional.

The callback function gets not ONE argument (as shown above), but actually 4 arguments. If you pass some standard function into forEach that can give you very strange results if the standard function happens to accept more than one argument and you did not know or think about it.

You get both index and array, so you can do horrible things like:

  array.forEach((current,i,array) => {
    const last = array[i-1];
    ..
  });

I have seen worse. Don’t do it. Functional programming is about being clear about your intentions. Use a good old for loop, or write your own higher-order-loop-function if you do the above thing often.

According to the documentation forEach loops in order. JavaScript is singlethreaded. But other languages may parallellize things like forEach, so I think the right way to think about forEach is that order should not matter. Best use for forEach are things like:

  ['gif','jpg','png'].forEach(registerImageFormat);
  players.forEach(updatePosition);

forEach is slower than the good old for-loop and for-of.

Sparse Arrays

I made an experment with a sparse (and worse) array:

  const array = ['first'];
  array[2] = 'last';
  array.x = 'off-side';
 
  let r = 'for';
  for ( let i=0 ; i<array.length ; i++ ) {
    r += ':' + array[i];
  }
  console.log(r);
 
  r = 'for-in';
  for ( const i in array ) {
    r += ':' + array[i];
  }
  console.log(r);

  r = 'for-of';
  for ( const x of array ) {
    r += ':' + x;
  }
  console.log(r);
 
  r = 'forEach';
  array.forEach((x) => {
    r += ':' + x;
  });
  console.log(r);

The output of this program is:

  for:first:undefined:last
  for-in:first:last:off-side
  for-of:first:undefined:last
  forEach:first:last

If this surprises you, think about how you code and what loops you use.

Performance

For a rather simple loop body here are some benchmarks

~160 M loopsMacBook Air 2014
node 14.16.0
RPI v2 900MHz
node 14.15.3
i7-8809G
node 12.18.3
i7-8809G
node 14.16.0
for ( i=0 ; i<array.length ; i++ )280ms3300ms200ms180ms
for ( i of array )440ms6500ms470ms340ms
for ( i in array )6100ms74000ms5900ms4100ms
array.forEach560ms10400ms480ms470ms

On one hand, a few milliseconds for a millions loops may not mean anything. On the other hand that could be a few milliseconds more latency or UI refresh delay.

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.