Control Flow and Loops
Making decisions and repeating work: if and else, switch, the ternary operator, for, while, for...of and for...in, and break and continue.
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)
|