JavaScript for impatient programmers
Please support this book: buy it or donate
(Ad, please don’t block.)

21 Control flow statements



This chapter covers the following control flow statements:

Before we get to the actual control flow statements, let’s take a look at two operators for controlling loops.

21.1 Conditions of control flow statements

if, while, and do-while have conditions that are, in principle, boolean. However, a condition only has to be truthy (true if coerced to boolean) in order to be accepted. In other words, the following two control flow statements are equivalent:

if (value) {}
if (Boolean(value) === true) {}

This is a list of all falsy values:

All other values are truthy. For more information, see §14.2 “Falsy and truthy values”.

21.2 Controlling loops: break and continue

The two operators break and continue can be used to control loops and other statements while you are inside them.

21.2.1 break

There are two versions of break: one with an operand and one without an operand. The latter version works inside the following statements: while, do-while, for, for-of, for-await-of, for-in and switch. It immediately leaves the current statement:

for (const x of ['a', 'b', 'c']) {
  console.log(x);
  if (x === 'b') break;
  console.log('---')
}

// Output:
// 'a'
// '---'
// 'b'

21.2.2 break plus label: leaving any labeled statement

break with an operand works everywhere. Its operand is a label. Labels can be put in front of any statement, including blocks. break foo leaves the statement whose label is foo:

foo: { // label
  if (condition) break foo; // labeled break
  // ···
}

In the following example, we use break with a label to leave a loop differently when we succeeded (line A). Then we skip what comes directly after the loop, which is where we end up if we failed.

function findSuffix(stringArray, suffix) {
  let result;
  search_block: {
    for (const str of stringArray) {
      if (str.endsWith(suffix)) {
        // Success:
        result = str;
        break search_block; // (A)
      }
    } // for
    // Failure:
    result = '(Untitled)';
  } // search_block

  return { suffix, result };
    // Same as: {suffix: suffix, result: result}
}
assert.deepEqual(
  findSuffix(['foo.txt', 'bar.html'], '.html'),
  { suffix: '.html', result: 'bar.html' }
);
assert.deepEqual(
  findSuffix(['foo.txt', 'bar.html'], '.mjs'),
  { suffix: '.mjs', result: '(Untitled)' }
);

21.2.3 continue

continue only works inside while, do-while, for, for-of, for-await-of, and for-in. It immediately leaves the current loop iteration and continues with the next one – for example:

const lines = [
  'Normal line',
  '# Comment',
  'Another normal line',
];
for (const line of lines) {
  if (line.startsWith('#')) continue;
  console.log(line);
}
// Output:
// 'Normal line'
// 'Another normal line'

21.3 if statements

These are two simple if statements: one with just a “then” branch and one with both a “then” branch and an “else” branch:

if (cond) {
  // then branch
}

if (cond) {
  // then branch
} else {
  // else branch
}

Instead of the block, else can also be followed by another if statement:

if (cond1) {
  // ···
} else if (cond2) {
  // ···
}

if (cond1) {
  // ···
} else if (cond2) {
  // ···
} else {
  // ···
}

You can continue this chain with more else ifs.

21.3.1 The syntax of if statements

The general syntax of if statements is:

if (cond) «then_statement»
else «else_statement»

So far, the then_statement has always been a block, but we can use any statement. That statement must be terminated with a semicolon:

if (true) console.log('Yes'); else console.log('No');

That means that else if is not its own construct; it’s simply an if statement whose else_statement is another if statement.

21.4 switch statements

A switch statement looks as follows:

switch («switch_expression») {
  «switch_body»
}

The body of switch consists of zero or more case clauses:

case «case_expression»:
  «statements»

And, optionally, a default clause:

default:
  «statements»

A switch is executed as follows:

21.4.1 A first example of a switch statement

Let’s look at an example: The following function converts a number from 1–7 to the name of a weekday.

function dayOfTheWeek(num) {
  switch (num) {
    case 1:
      return 'Monday';
    case 2:
      return 'Tuesday';
    case 3:
      return 'Wednesday';
    case 4:
      return 'Thursday';
    case 5:
      return 'Friday';
    case 6:
      return 'Saturday';
    case 7:
      return 'Sunday';
  }
}
assert.equal(dayOfTheWeek(5), 'Friday');

21.4.2 Don’t forget to return or break!

At the end of a case clause, execution continues with the next case clause, unless you return or break – for example:

function englishToFrench(english) {
  let french;
  switch (english) {
    case 'hello':
      french = 'bonjour';
    case 'goodbye':
      french = 'au revoir';
  }
  return french;
}
// The result should be 'bonjour'!
assert.equal(englishToFrench('hello'), 'au revoir');

That is, our implementation of dayOfTheWeek() only worked because we used return. We can fix englishToFrench() by using break:

function englishToFrench(english) {
  let french;
  switch (english) {
    case 'hello':
      french = 'bonjour';
      break;
    case 'goodbye':
      french = 'au revoir';
      break;
  }
  return french;
}
assert.equal(englishToFrench('hello'), 'bonjour'); // ok

21.4.3 Empty case clauses

The statements of a case clause can be omitted, which effectively gives us multiple case expressions per case clause:

function isWeekDay(name) {
  switch (name) {
    case 'Monday':
    case 'Tuesday':
    case 'Wednesday':
    case 'Thursday':
    case 'Friday':
      return true;
    case 'Saturday':
    case 'Sunday':
      return false;
  }
}
assert.equal(isWeekDay('Wednesday'), true);
assert.equal(isWeekDay('Sunday'), false);

21.4.4 Checking for illegal values via a default clause

A default clause is jumped to if the switch expression has no other match. That makes it useful for error checking:

function isWeekDay(name) {
  switch (name) {
    case 'Monday':
    case 'Tuesday':
    case 'Wednesday':
    case 'Thursday':
    case 'Friday':
      return true;
    case 'Saturday':
    case 'Sunday':
      return false;
    default:
      throw new Error('Illegal value: '+name);
  }
}
assert.throws(
  () => isWeekDay('January'),
  {message: 'Illegal value: January'});

  Exercises: switch

21.5 while loops

A while loop has the following syntax:

while («condition») {
  «statements»
}

Before each loop iteration, while evaluates condition:

21.5.1 Examples of while loops

The following code uses a while loop. In each loop iteration, it removes the first element of arr via .shift() and logs it.

const arr = ['a', 'b', 'c'];
while (arr.length > 0) {
  const elem = arr.shift(); // remove first element
  console.log(elem);
}
// Output:
// 'a'
// 'b'
// 'c'

If the condition always evaluates to true, then while is an infinite loop:

while (true) {
  if (Math.random() === 0) break;
}

21.6 do-while loops

The do-while loop works much like while, but it checks its condition after each loop iteration, not before.

let input;
do {
  input = prompt('Enter text:');
  console.log(input);
} while (input !== ':q');

prompt() is a global function that is available in web browsers. It prompts the user to input text and returns it.

21.7 for loops

A for loop has the following syntax:

for («initialization»; «condition»; «post_iteration») {
  «statements»
}

The first line is the head of the loop and controls how often the body (the remainder of the loop) is executed. It has three parts and each of them is optional:

A for loop is therefore roughly equivalent to the following while loop:

«initialization»
while («condition») {
  «statements»
  «post_iteration»
}

21.7.1 Examples of for loops

As an example, this is how to count from zero to two via a for loop:

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

// Output:
// 0
// 1
// 2

This is how to log the contents of an Array via a for loop:

const arr = ['a', 'b', 'c'];
for (let i=0; i<arr.length; i++) {
  console.log(arr[i]);
}

// Output:
// 'a'
// 'b'
// 'c'

If you omit all three parts of the head, you get an infinite loop:

for (;;) {
  if (Math.random() === 0) break;
}

21.8 for-of loops

A for-of loop iterates over an iterable – a data container that supports the iteration protocol. Each iterated value is stored in a variable, as specified in the head:

for («iteration_variable» of «iterable») {
  «statements»
}

The iteration variable is usually created via a variable declaration:

const iterable = ['hello', 'world'];
for (const elem of iterable) {
  console.log(elem);
}
// Output:
// 'hello'
// 'world'

But you can also use a (mutable) variable that already exists:

const iterable = ['hello', 'world'];
let elem;
for (elem of iterable) {
  console.log(elem);
}

21.8.1 const: for-of vs. for

Note that in for-of loops you can use const. The iteration variable can still be different for each iteration (it just can’t change during the iteration). Think of it as a new const declaration being executed each time in a fresh scope.

In contrast, in for loops you must declare variables via let or var if their values change.

21.8.2 Iterating over iterables

As mentioned before, for-of works with any iterable object, not just with Arrays – for example, with Sets:

const set = new Set(['hello', 'world']);
for (const elem of set) {
  console.log(elem);
}

21.8.3 Iterating over [index, element] pairs of Arrays

Lastly, you can also use for-of to iterate over the [index, element] entries of Arrays:

const arr = ['a', 'b', 'c'];
for (const [index, elem] of arr.entries()) {
  console.log(`${index} -> ${elem}`);
}
// Output:
// '0 -> a'
// '1 -> b'
// '2 -> c'

With [index, element], we are using destructuring to access Array elements.

  Exercise: for-of

exercises/control-flow/array_to_string_test.mjs

21.9 for-await-of loops

for-await-of is like for-of, but it works with asynchronous iterables instead of synchronous ones. And it can only be used inside async functions and async generators.

for await (const item of asyncIterable) {
  // ···
}

for-await-of is described in detail in the chapter on asynchronous iteration.

21.10 for-in loops (avoid)

  Recommendation: don’t use for-in loops

for-in has several pitfalls. Therefore, it is usually best to avoid it.

This is an example of using for-in properly, which involves boilerplate code (line A):

function getOwnPropertyNames(obj) {
  const result = [];
  for (const key in obj) {
    if ({}.hasOwnProperty.call(obj, key)) { // (A)
      result.push(key);
    }
  }
  return result;
}
assert.deepEqual(
  getOwnPropertyNames({ a: 1, b:2 }),
  ['a', 'b']);
assert.deepEqual(
  getOwnPropertyNames(['a', 'b']),
  ['0', '1']); // strings!

We can implement the same functionality without for-in, which is almost always better:

function getOwnPropertyNames(obj) {
  const result = [];
  for (const key of Object.keys(obj)) {
    result.push(key);
  }
  return result;
}

  Quiz

See quiz app.