if and else

Control flow decides which statements run and how often. The general ideas (sequence, selection and iteration) are on Control Structures; this page shows the JavaScript syntax and its quirks.

An if statement runs a block only when its condition is true. The condition must be in parentheses, and the block is wrapped in curly braces. else gives an alternative, and else if chains further tests; JavaScript has no elif keyword. The first matching branch wins, and the rest are skipped.

function classify(tempC) {
  if (tempC >= 30) {
    return 'heat alert';
  } else if (tempC >= 20) {
    return 'warm';
  } else if (tempC >= 5) {
    return 'normal';
  } else {
    return 'frost alert';
  }
}

for (const t of [34, 21.5, 12, -3]) {
  console.log(t, classify(t));
}
34 heat alert
21.5 warm
12 normal
-3 frost alert

Indentation means nothing to JavaScript; the braces define the block. Braces are optional when a block has a single statement, but leaving them out invites bugs when someone later adds a second line that only looks as if it belongs to the if. Most style guides require braces always.

Truthy and falsy conditions

A condition does not have to be a boolean. JavaScript converts it: the values false, 0, -0, 0n, "" (the empty string), null, undefined and NaN are falsy, and everything else is truthy. The full rules, and the operators that build on them, are on Operators, Comparison and Type Coercion.

const { inspect } = require('node:util'); // shows values as Node's console does

const values = [0, '', '0', 'false', [], {}, null, undefined, NaN, 42];
for (const value of values) {
  console.log(inspect(value).padEnd(10), value ? 'truthy' : 'falsy');
}

const openTickets = [];
if (openTickets.length === 0) {
  console.log('No open tickets');
}
0          falsy
''         falsy
'0'        truthy
'false'    truthy
[]         truthy
{}         truthy
null       falsy
undefined  falsy
NaN        falsy
42         truthy
No open tickets

The conditional (ternary) operator

For choosing between two values, the conditional expression condition ? valueIfTrue : valueIfFalse is shorter than an if statement. It is the only JavaScript operator with three operands, hence its nickname "the ternary". It is an expression, so you can use it inside a template literal, an argument or an assignment.

const affected = 412;
const priority = affected > 100 ? 'P1' : 'P3';
console.log(`Priority ${priority}`);

const count = 1;
console.log(`${count} outage${count === 1 ? '' : 's'} reported`);
Priority P1
1 outage reported

Ternaries can be chained, but more than two levels quickly become hard to read. For several branches, an if chain, a switch or a lookup object is usually clearer.

switch

A switch statement compares one value against a list of case values and jumps to the first one that matches; default runs when none does. The comparison is strict equality (===), so the string "1" does not match case 1.

Once a case matches, execution continues through all the following cases until it reaches a break (or a return). This fall-through lets several cases share one block, but a forgotten break is one of the most common bugs in JavaScript.

function responseTarget(priority) {
  switch (priority) {
    case 'P1':
      return '15 minutes';
    case 'P2':
      return '1 hour';
    case 'P3':
    case 'P4':           // no code: falls through to P5's block
    case 'P5':
      return 'next business day';
    default:
      return 'unknown priority';
  }
}

console.log(responseTarget('P1'), '|', responseTarget('P4'), '|', responseTarget('p1'));
15 minutes | next business day | unknown priority

Here every case ends in return, which also leaves the switch. Without return or break, the code runs on into the next case:

const status = 'assigned';

switch (status) {
  case 'new':
    console.log('Log the ticket');
  case 'assigned':
    console.log('Notify the engineer');
  case 'resolved':
    console.log('Send the satisfaction survey'); // runs too: no break above
    break;
  default:
    console.log('Unknown status');
}
Notify the engineer
Send the satisfaction survey

while and do...while

A while loop checks its condition before each round and runs as long as the condition is true. It suits loops where you do not know the number of rounds in advance. If the condition never becomes false, you have an infinite loop; make sure something in the body moves toward the end.

// Battery storage discharging 15% per hour until it reaches the 20% reserve
let charge = 100;
let hours = 0;
while (charge - 15 >= 20) {
  charge -= 15;
  hours++;
}
console.log(`${hours} hours, ${charge}% left`);
5 hours, 25% left

do...while

A do...while loop checks its condition after each round, so the body always runs at least once. That fits "try, then decide whether to try again": retries, reading input until it is valid, or menus. Python has no equivalent; there you write while True: with a break. Note the semicolon after the final while (…).

// Simulated responses from a meter that sometimes times out
const responses = ['timeout', 'timeout', 'ok'];
let attempt = 0;
let result;

do {
  result = responses[attempt];
  attempt++;
  console.log(`Attempt ${attempt}: ${result}`);
} while (result !== 'ok' && attempt < 5);
Attempt 1: timeout
Attempt 2: timeout
Attempt 3: ok

The classic for loop

The C-style for loop has three parts in its parentheses, separated by semicolons: an initialization that runs once, a condition checked before each round, and an update that runs after each round. It is the standard way to count, to step by more than one, or to walk an array backward.

for (let hour = 0; hour < 24; hour += 6) {
  console.log(`${String(hour).padStart(2, '0')}:00 reading`);
}

const meters = ['KE-0040', 'KE-0041', 'KE-0042'];
for (let i = meters.length - 1; i >= 0; i--) {
  console.log(i, meters[i]);
}
00:00 reading
06:00 reading
12:00 reading
18:00 reading
2 KE-0042
1 KE-0041
0 KE-0040

Declare the counter with let: it then exists only inside the loop, and each round gets its own copy. That matters when the body creates functions that use the counter later, as explained under closures in loops. i++ adds one to i and i-- subtracts one.

Looping over collections

for...of: values

A for...of loop (ES2015) runs once for each value of an iterable: an array, a string, a Map, a Set and more. It is the JavaScript counterpart of Python's for x in items, and the loop to reach for by default. Over a string, it gives each Unicode code point, keeping surrogate pairs together (see Strings and Template Literals).

const readings = [318.4, 290.1, 305.7];
let total = 0;
for (const kwh of readings) {
  total += kwh;
}
console.log(total.toFixed(1));

// entries() gives [index, value] pairs, like Python's enumerate()
for (const [i, kwh] of readings.entries()) {
  console.log(`week ${i + 1}: ${kwh}`);
}

for (const ch of 'EV🔌') {
  console.log(ch);
}

const tariffs = new Map([['day', 0.29], ['night', 0.21]]);
for (const [period, price] of tariffs) {
  console.log(period, price);
}
914.2
week 1: 318.4
week 2: 290.1
week 3: 305.7
E
V
🔌
day 0.29
night 0.21

Use const for the loop variable when the body does not reassign it: each round gets a fresh binding. A plain object is not iterable, so for (const x of {a: 1}) throws a TypeError; loop over Object.entries(obj) instead.

for...in: keys

A for...in loop runs once for each enumerable property name (key) of an object, including names inherited through its prototype chain. The keys are always strings. It was the original way to walk an object's properties, and it is still fine for plain data objects.

const outage = { ticket: 'INC-31877', town: 'Delft', customers: 412 };

for (const key in outage) {
  console.log(key, '=', outage[key]);
}

// Modern alternative: Object.entries gives [key, value] pairs
for (const [key, value] of Object.entries(outage)) {
  console.log(`${key}: ${value}`);
}
ticket = INC-31877
town = Delft
customers = 412
ticket: INC-31877
town: Delft
customers: 412

Do not use for...in on arrays. It gives the indexes as strings, so arithmetic on them concatenates, and it also visits any extra properties someone added to the array.

const towns = ['Delft', 'Gouda'];

for (const i in towns) {
  console.log(i, typeof i, i + 1);   // '0' + 1 is '01'
}
for (const town of towns) {
  console.log(town);
}
0 string 01
1 string 11
Delft
Gouda

break and continue

The break statement ends the loop immediately; the program continues after it. continue skips the rest of the current round and starts the next one. Both work in every loop type: for, for...of, for...in, while and do...while.

const queue = ['INC-31870', 'skip', 'INC-31871', 'STOP', 'INC-31872'];

for (const item of queue) {
  if (item === 'skip') {
    continue;          // ignore this entry, go on with the next
  }
  if (item === 'STOP') {
    break;             // leave the loop entirely
  }
  console.log('Processing', item);
}
console.log('Done');
Processing INC-31870
Processing INC-31871
Done

Labels for nested loops

A plain break only leaves the innermost loop. To leave an outer loop from inside an inner one, put a label in front of the outer loop. A labeled statement is a statement prefixed with a name and a colon, such as search:; break search and continue search then refer to that loop. Labels are rare in everyday code; moving the nested loops into a function and using return is often clearer.

const substations = {
  North: ['ok', 'ok', 'ok'],
  South: ['ok', 'fault', 'ok'],
  West: ['ok', 'ok'],
};

search: for (const [name, feeders] of Object.entries(substations)) {
  for (const [i, state] of feeders.entries()) {
    if (state === 'fault') {
      console.log(`First fault: ${name}, feeder ${i + 1}`);
      break search;    // stops both loops; West is never checked
    }
  }
  console.log(`${name}: all clear`);
}
North: all clear
First fault: South, feeder 2

Loops and array methods

Much JavaScript code processes arrays with methods such as forEach(), map(), filter() and find() instead of an explicit loop; they are covered on Arrays. They express what you want and avoid index bookkeeping. One difference matters for control flow: forEach() calls a function for each element, so break and continue do not work inside it, and return only ends the current call. When you need to stop early, use for...of with break, or a method that stops by itself, such as find() or some().

const temps = [12.4, 15.1, 31.2, 17.3, 33.0];

const firstHot = temps.find((t) => t > 30);       // stops at the first match
console.log(firstHot);

temps.forEach((t) => {
  if (t < 15) return;           // like continue: skips this element only
  console.log('checked', t);
});
31.2
checked 15.1
checked 31.2
checked 17.3
checked 33
You want to… Use
Go through the values of an array, string, Map or Set for...of
Also know the index for (const [i, v] of arr.entries()) or a classic for
Count, step or go backward classic for
Repeat until a condition changes while; do...while if it must run once
Go through an object's keys and values for...of Object.entries(obj), or for...in
Build a new array from an old one map(), filter() (see Arrays)