Loops and iteration

Introduction

In computing a loop specifies iteration. With iteration a statement is executed repeatedly a specified number of times, once for each item in a collection or until some condition is met. A modern high-level programming language generally supports count-controlled loops (for loops), condition-controlled loops (while loops and do...while loops) and collection-controlled loops (for each loops).

Recursion is closely related to iteration. Recursion is implemented by a function calling itself. Recursion will be discussed in a later chapter. Iteration and recursion are key elements of algorithms.

for statement

The JavaScript syntax of the count-controlled for loop is:


for (initialization; condition; increment) statement;

After the for keyword three expressions enclosed in parentheses follow. They are separated by semicolons. The expressions are optional, but the semicolons are not. There must be precisely two semicolons within the parentheses. The three optional expressions are:

initialization:
This expression is evaluated once, before the loop begins. Typically this expression declares and initializes one or more loop counter variables.
condition:
This expression is evaluated before each loop iteration. If the expression is truthy, the statement is executed, if the expression is falsy, the loop terminates. If this expression is omitted, it is assumed to be truthy and the loop will never stop (an infinite loop), unless the statement contains code to break out of the loop.
increment:
This expression is evaluated at the end of each loop iteration. That is, after executing the statement and before the next evaluation of condition. Typically this expression updates or increments the counter variable(s).

The statement (usually a block statement) is the statement to be repeated.

Next an example of how a simple for-loop typically is arranged. The statement successively logs the digits 0 to 9. Note that you can also use ++i instead of i++.


for (let i = 0; i <= 9; i++) {
   console.log(i);
}

The next example does the same, but now the initialization is done outside the loop and the condition and increment expressions are placed inside the repeating block statement.


let i = 0;
for (;;) {
   if (i <= 9) { 
     console.log(i);
     i++;
   } else break; // do not forget to include this break!! 
}

Warning: the condition expression is now moved from inside the parentheses to the statement. This means that you need to use a break statement to explicitly break out the loop if the condition becomes falsy. Otherwise you have created an infinite loop which will stall your application and computer when running the code.

As an exercise: what will be logged to the console in the next two scripts?


for (let i = 0, n = 0; i < 5; ++i){
  console.log(n += i);
}

for (let i = 0, j = 0; i < 5; ++i){
   for (; j < 5; ++j){
      console.log(i+", "+j);
   }
}

for...in statement

The JavaScript collection-controlled for...in statement iterates over the properties of an object.


const myObject = { a: 1, b: 2, c: 3 };
for (let propertyName in myObject) {
  console.log(propertyName + ": " + myObject[propertyName]);
}
// logs:
// "a: 1"
// "b: 2"
// "c: 3"

A different property name (a string) is assigned to propertyName on each iteration. The loop terminates once the statement has been repeated for every property.

Three things in particular are important to keep in mind:

Use Object.hasOwn() to iterate over the object's own properties only:


const myObject = { foo: true, bar: true };
for (let propertyName in myObject) {
  if (Object.hasOwn(myObject, propertyName)) {
    console.log(propertyName + ": " + myObject[propertyName]);
  }
}

BTW: This could also have been achieved by using hasOwnProperty(), but Object.hasOwn() is less prone to problems and therefore recommended. It is also possible to create an array with all object's own properties by using Object.keys() or Object.getOwnPropertyNames().

Technically a for...in loop can also be used to iterate over array elements or over strings. But a for loop, for...of loop or Array.prototype.forEach() method are better options for these purposes (see next sections).

for...of statement

The JavaScript collection-controlled for...of statement iterates over iterable objects in a successive ascending order. Iterable objects are objects with a special "iterator" method, such as Array, Map and Set (later this tutorial more about this). Unlike the for...in statement, the for...of statement does not work on objects in general, only on iterable objects.

Iterating over an Array


const myArray = [10, 20, 30];

for (let elementValue of myArray) {
   console.log(elementValue);
}
// logs:
// 10
// 20
// 30

The elements of an array are objects with properties with successive integer names (indexes), from zero to the end of the list. In that order, a next property value is assigned to elementValue on each iteration. Unlike with the for...in statement, with a for...of loop the values, instead of the property names (indexes), are assigned to elementValue. The loop terminates once the statement has been repeated for every successive property.

Iterating over a String

As mentioned above, for...of loops iterate over iterables. Examples of iterables are Array, Map and Set. But also a string (via wrapper object String) is an iterable. In next example a for...of loop iterates over a string.


const string = 'LOL!';
for (let character of string) {
  console.log(character);
}

// Logs:
// "L"
// "O"
// "L"
// "!"

The for...of loop works correctly on strings with surrogate pairs.


const string = '😍🀩'; // surrogate pairs
for (let character of string) {
  console.log(character);
}

// Logs:
// "😍"
// "🀩"

However, strings with assembled characters will break. More sophisticated methods are needed to distinguish the separate assembled characters in a string (see regular expressions).


const string = 'πŸ‘§πŸΏπŸ‘¨πŸΏβ€β€'; // assembled characters
for (let character of string) {
  console.log(character);
}

// Logs:
// "πŸ‘§"
// "🏿"
// "πŸ‘¨"
// "🏿"

Alternatives for the for...of loop

A common for loop can often be used as an alternative for the for...of loop, as shown in the next example. In the conditions we use the length property, respectively inherited from the Array prototype chain and via the wrapper object String.


const myArray = [10, 20, 30];
for (let i = 0; i < myArray.length; i++) {
   console.log(myArray[i]);
}

const string = 'LOL!';
for (let i = 0; i < string.length; i++) {
  console.log(string.charAt(i));
}

However, not every iterable has a length property, so a common for loop cannot be used on all iterables. And a common for loop is not Unicode-aware when looping over a string.


const string = '😍🀩'; // surrogate pairs
for (let i = 0; i < string.length; i++) {
  console.log(string.charAt(i));
}

// Logs:
// "οΏ½"
// "οΏ½"
// "οΏ½"
// "οΏ½"

Of course, we could get around this by using a Unicode-aware method, such as Array.from(), to create an additional array with each character as one element. However, that would be unnecessarily complicated since we have the Unicode-aware for...of loop.

Array also inherits a forEach() method which can be used to iterate over an array.


const myArray = [10, 20, 30];
myArray.forEach(function(element) {
  console.log(element)
})
// Logs:
// 10
// 20
// 30

Technically you can also use the Array.prototype.map() method. This method creates a new array populated with the results of calling a provided function on every element in the calling array. But using this method is an anti-pattern if the returned array is not used (as in this case) and/or if the callback does not return a value (as in this case).


const myArray = [10, 20, 30];
// Do not do this:
myArray.map(function(element) {
  console.log(element)
})
// Logs:
// 10
// 20
// 30

while statement

The JavaScript syntax of the condition-controlled while loop is:


while (condition) statement;
condition:
This expression is evaluated before each loop iteration. If the expression is truthy, the statement is executed, if the expression is falsy, the loop terminates.
statement:
This is the statement (usually a block statement) to be repeated.

Next an example:


let n = 0;
while (n < 3) {
   console.log(n);
   n++;
}

// logs:
// 0
// 1
// 2

What would have been logged to the console if n++; was before console.log(n);?

Note that any while loop can also be constructed with a for loop and that also with a while loop you can create an infinite loop:


// do not run this code! 
while(true);

do...while statement

The JavaScript syntax of the condition-controlled do...while loop is:


do statement while (condition);
statement:
This is the statement (usually a block statement) to be repeated. The statement is executed at least once.
condition:
This expression is evaluated after each loop iteration. If the expression is truthy, the statement is re-executed, if the expression is falsy, the loop terminates.

Next an example:


let n = 0;
do {
   console.log(n);
   n++;
}
while (n < 3);

// logs:
// 0
// 1
// 2

What would have been logged to the console if n++; was before console.log(n);?

Note that the statement is always executed at least once, even if the condition is false under all circumstances. Also note that with a do...while loop you can also create an infinite loop.


const condition = false;
do console.log("This will be logged, even though the condition is false")
while (condition);

continue, break and label

continue and break


let txt = '';

for (let i = 0; i < 5; i++) {
   if (i === 3) {
      continue;
   }
   txt = txt + i;
}

console.log(txt); // logs: "0124"

In the example above the for loop iterates over the statement, but breaks out of the iteration where i === 3.

The continue statement breaks out of one (the current) iteration, the break statement breaks out of the entire loop.

The continue statement can be used inside the body (the repeated statement) of a loop. The break statement can be used inside the body of a loop or switch statement.

In a for loop the increment expression is evaluated at the end of each loop iteration. When a continue statement breaks out of an iteration, the increment expression will still be evaluated.

Using continue with a label

The continue statement can be used with a label reference. A label adds an identifier to a loop, like myLabel:for(;;);. You can refer to that identifier in a continue statement, like continue myLabel. The label statement is used in nested loops. Next example shows how this works.


myLabel:
for (let i = 0; i < 3; ++i) { // labeled loop
   for (let j = 0; j < 3; ++j) { // nested loop
      if (i === 1 && j === 1) {
         continue myLabel;
      }
      console.log(i+", "+j);
   }
}

// logs:
// "0, 0"
// "0, 1"
// "0, 2"
// "1, 0"
// "2, 0"
// "2, 1"
// "2, 2"

In the example above the continue statement breaks out of the current iteration, but not of the nested loop where the continue statement sits. It breaks out of the current iteration of the labeled (the parent) loop. In other words; it jumps to the next iteration of the outer (labeled) loop instead of to the next iteration of the inner (nested) loop. If the continue statement would not have had a label reference, it would only have skipped one iteration in the inner loop (only "1, 1") and not two iterations in the inner loop (not both "1, 1" and "1, 2").

Using break with a label

Previously we already used the break statement in switch statements and in loops without a condition expression. A break statement breaks out of the directly enclosing for, while or do...while loop. A break statement can also be used with a label reference in a nested loop, much like how a continue statement with a label works.


myLabel:
for (let i = 0; i < 3; ++i) { // labeled loop
   for (let j = 0; j < 3; ++j) { // nested loop
      if (i === 1 && j === 1) {
         break myLabel;
      }
      console.log(i+", "+j);
   }
}

// logs:
// "0, 0"
// "0, 1"
// "0, 2"
// "1, 0"

In the example above break myLabel terminates the outer (labeled) loop when i === 1 && j === 1. It terminates the entire outer loop where the continue myLabel statement earlier only terminated the current iteration of the outer loop. If the break statement had no label reference, it would only have terminated the inner loop. Note that in this example, using either break (without a label) or continue myLabel will have the same effect:


myLabel:
for (let i = 0; i < 3; ++i) {
   for (let j = 0; j < 3; ++j) {
      if (i === 1 && j === 1) {
         break; // or: continue myLabel
      }
      console.log(i+", "+j);
   }
}

// logs:
// "0, 0"
// "0, 1"
// "0, 2"
// "1, 0"
// "2, 0"
// "2, 1"
// "2, 2"