Beginner

Control Flow in JavaScript

Master conditionals, switch statements, loops, and loop control in JavaScript with practical Docker-ready Node.js examples

Control flow is how a program decides what to do and how many times to do it. Without it, code would simply run top to bottom with no branching or repetition. JavaScript gives you a familiar, C-inspired set of structured control-flow tools: if/else, switch, and several flavors of loops.

As a multi-paradigm, dynamically-typed language, JavaScript layers a few distinctive features on top of the classic structures. Conditions are evaluated using truthiness rather than requiring a strict boolean, so values like 0, "", null, and undefined all count as “false.” The logical operators &&, ||, and the nullish-coalescing ?? operator short-circuit, which lets you express defaults and guards concisely. And because JavaScript is weakly typed, understanding how values coerce inside a condition is essential to writing correct branches.

This tutorial walks through conditionals, the switch statement, every loop form (for, while, do...while, for...of, for...in), loop control with break and continue, and the truthiness rules that make JavaScript conditions behave the way they do. Every example is a standalone file you can run with Node.js or Docker.

Conditionals: if, else if, else, and the Ternary

The if statement runs a block only when its condition is truthy. Chain else if for additional branches and else for a fallback. For simple either/or choices, the ternary operator condition ? a : b produces a value inline.

Create a file named conditionals.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Conditional statements in JavaScript

const temperature = 72;

// Basic if / else if / else
if (temperature < 32) {
  console.log("Freezing");
} else if (temperature < 60) {
  console.log("Cold");
} else if (temperature < 80) {
  console.log("Comfortable");
} else {
  console.log("Hot");
}

// Ternary (conditional) expression returns a value
const status = temperature >= 60 ? "warm" : "cool";
console.log(`It feels ${status}`);

// Chained ternary for a simple letter grade
const score = 85;
const grade =
  score >= 90 ? "A" :
  score >= 80 ? "B" :
  score >= 70 ? "C" : "F";
console.log(`Grade: ${grade}`);

The if chain evaluates conditions top to bottom and runs the first matching block. The ternary is an expression, so it can be assigned directly to a variable — unlike if, which is a statement.

The switch Statement

When you compare a single value against many fixed possibilities, switch is often clearer than a long if/else if chain. Each case is compared using strict equality (===). A break ends the switch; omitting it causes fall-through, which is useful for grouping cases that share behavior.

Create a file named switch_demo.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// The switch statement

const day = 3;
let name;

switch (day) {
  case 1:
    name = "Monday";
    break;
  case 2:
    name = "Tuesday";
    break;
  case 3:
    name = "Wednesday";
    break;
  default:
    name = "Unknown";
}
console.log(`Day ${day} is ${name}`);

// Intentional fall-through: cases 12, 1, 2 all share one block
const month = 4;
let season;

switch (month) {
  case 12:
  case 1:
  case 2:
    season = "Winter";
    break;
  case 3:
  case 4:
  case 5:
    season = "Spring";
    break;
  default:
    season = "Summer or Autumn";
}
console.log(`Month ${month} is in ${season}`);

Because case uses strict equality, switch (1) will not match case "1" — the number and string are different types. The default clause handles any unmatched value and can appear anywhere, though it’s conventionally placed last.

Loops: for, while, do…while, for…of, for…in

JavaScript offers several looping constructs. The classic for loop gives explicit control over a counter. while repeats as long as a condition holds, and do...while is its cousin that always runs the body at least once. For collections, for...of iterates over the values of an iterable (like an array), while for...in iterates over the keys of an object.

Create a file named loops.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Loops in JavaScript

// Classic C-style for loop with a counter
for (let i = 1; i <= 3; i++) {
  console.log(`for loop: ${i}`);
}

// while loop: checks the condition before each iteration
let count = 3;
while (count > 0) {
  console.log(`while count: ${count}`);
  count--;
}

// do...while: runs the body once before checking the condition
let n = 0;
do {
  console.log(`do...while: ${n}`);
  n++;
} while (n < 2);

// for...of iterates over the VALUES of an array
const colors = ["red", "green", "blue"];
for (const color of colors) {
  console.log(`color: ${color}`);
}

// for...in iterates over the KEYS of an object
const person = { name: "Ada", role: "engineer" };
for (const key in person) {
  console.log(`${key} = ${person[key]}`);
}

A common mistake is using for...in on an array expecting the elements — it actually yields the indices as strings. Use for...of for array values and for...in for object property names.

Loop Control: break, continue, and Labels

break exits a loop immediately, and continue skips the rest of the current iteration and moves to the next. For nested loops, a label lets break or continue target an outer loop instead of the innermost one.

Create a file named loop_control.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Controlling loop execution

// break exits the loop entirely
for (let i = 1; i <= 10; i++) {
  if (i > 5) {
    break;
  }
  console.log(`break demo: ${i}`);
}

// continue skips to the next iteration
for (let i = 1; i <= 6; i++) {
  if (i % 2 === 0) {
    continue; // skip even numbers
  }
  console.log(`odd number: ${i}`);
}

// A label lets break target an outer loop
outer: for (let row = 1; row <= 3; row++) {
  for (let col = 1; col <= 3; col++) {
    if (row + col === 4) {
      console.log(`stopping at row ${row}, col ${col}`);
      break outer;
    }
  }
}

Without the outer: label, break would only exit the inner col loop and the outer loop would keep running. Labeled statements are rarely needed, but they’re the cleanest way to break out of deeply nested iteration.

Truthiness and Short-Circuit Logic

JavaScript conditions don’t require a boolean — any value is coerced to true or false. The falsy values are exactly: false, 0, -0, 0n, "", null, undefined, and NaN. Everything else is truthy. The || operator returns the first truthy operand (great for defaults), while ?? (nullish coalescing) only falls back when the left side is null or undefined — not for other falsy values like 0.

Create a file named truthiness.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Truthy/falsy values and short-circuit logic

// Falsy values: false, 0, "", null, undefined, NaN
const values = [0, "", "hello", null, 42];
for (const v of values) {
  if (v) {
    console.log(`${JSON.stringify(v)} is truthy`);
  } else {
    console.log(`${JSON.stringify(v)} is falsy`);
  }
}

// || returns the first truthy value — handy for defaults
const userName = "";
const displayName = userName || "Guest";
console.log(`Welcome, ${displayName}`);

// ?? only falls back on null or undefined, NOT on 0 or ""
const count = 0;
const fallbackOr = count || 10;      // 0 is falsy   -> 10
const fallbackNullish = count ?? 10; // 0 is defined -> 0
console.log(`OR result: ${fallbackOr}`);
console.log(`Nullish result: ${fallbackNullish}`);

This example highlights a subtle but important distinction: count || 10 gives 10 because 0 is falsy, but count ?? 10 gives 0 because the value is neither null nor undefined. Reach for ?? when 0, "", or false are legitimate values you want to keep.

Running with Docker

You can run every example without installing Node.js locally by using the official Node.js Alpine image.

1
2
3
4
5
6
7
8
9
# Pull the official image
docker pull node:22-alpine

# Run each example
docker run --rm -v $(pwd):/app -w /app node:22-alpine node conditionals.js
docker run --rm -v $(pwd):/app -w /app node:22-alpine node switch_demo.js
docker run --rm -v $(pwd):/app -w /app node:22-alpine node loops.js
docker run --rm -v $(pwd):/app -w /app node:22-alpine node loop_control.js
docker run --rm -v $(pwd):/app -w /app node:22-alpine node truthiness.js

If you have Node.js installed locally (v18+), run any file directly, for example: node conditionals.js.

Expected Output

$ node conditionals.js
Comfortable
It feels warm
Grade: B

$ node switch_demo.js
Day 3 is Wednesday
Month 4 is in Spring

$ node loops.js
for loop: 1
for loop: 2
for loop: 3
while count: 3
while count: 2
while count: 1
do...while: 0
do...while: 1
color: red
color: green
color: blue
name = Ada
role = engineer

$ node loop_control.js
break demo: 1
break demo: 2
break demo: 3
break demo: 4
break demo: 5
odd number: 1
odd number: 3
odd number: 5
stopping at row 1, col 3

$ node truthiness.js
0 is falsy
"" is falsy
"hello" is truthy
null is falsy
42 is truthy
Welcome, Guest
OR result: 10
Nullish result: 0

Key Concepts

  • Conditions use truthiness, not strict booleans0, "", null, undefined, NaN, and false are falsy; everything else is truthy.
  • The ternary ? : is an expression — it produces a value you can assign, unlike if, which is a statement.
  • switch compares with strict equality (===) — types must match, and missing break causes intentional (or accidental) fall-through.
  • Pick the right loop for the jobfor for counters, while/do...while for condition-driven repetition, for...of for array values, for...in for object keys.
  • for...in yields keys, not values — using it on an array gives string indices, which is a frequent source of bugs; prefer for...of for arrays.
  • break and continue control loopsbreak exits, continue skips an iteration, and labels let them target an outer loop in nested code.
  • || vs ?? matters|| falls back on any falsy value, while ?? only falls back on null/undefined, preserving valid 0 and "" values.

Running Today

All examples can be run using Docker:

docker pull node:22-alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining