Skip to main content

Menu

System onlineAI Tutorials / 2026

AI Tutorials
Knowledge search

Recent · Popular · Tutorials

  • dart3 tutorials
  • java1 tutorial
  • Javascript3 tutorials
  • Node.js Introduction2 tutorials
  • dartdart · General · In depth
  • JavascriptJavascript · Advanced · In depth
  • JavascriptJavascript · Advanced · Mid
  • JavascriptJavascript · Advanced · Short

Find a tutorial

Tell the assistant what you want to learn. It suggests published chapters.

For example: “I want to learn Rust ownership from the start.” The conversation is kept only in this tab.

0 / 2,000 characters

Enter to send, Shift+Enter for a new line. Do not include personal information.

Chapter 3

Control Flow

At an advanced level, control flow in JavaScript is less about `if` and `for` syntax and more about the gotchas: implicit coercion in conditions, `switch` fallthrough, block scoping in loops, and the closure traps they create.
Level: AdvancedLength: Mid2 min read

Read at another depth

Control Flow

At an advanced level, control flow in JavaScript is less about if and for syntax and more about the gotchas: implicit coercion in conditions, switch fallthrough, block scoping in loops, and the closure traps they create.

Prerequisites assumed: you know values and types, coercion rules, and function scoping from earlier chapters.

Truthiness and coercion in conditions

Any value in a condition is coerced to boolean. The falsy set is short: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy — including "0", [], {}, and new Boolean(false).

Code example in javascript
if ([]) console.log('truthy');       // runs
if ('0') console.log('truthy');      // runs
if (new Boolean(false)) console.log('runs'); // runs

Decision criterion: use explicit comparisons (count !== 0, arr.length > 0) at boundaries where a falsy-but-valid value would silently diverge. Reserve truthy checks for idiomatic guards like if (user?.name).

switch: strict comparison and fallthrough

switch compares with ===, never coerces, and falls through without break.

Code example in javascript
function score(n) {
  switch (n) {
    case 1:
    case 2:
      return 'low';
    case 3:
      return 'high';
    default:
      return 'unknown';
  }
}
score('1'); // 'unknown' — strict comparison

Intentional fallthrough (grouping cases) is fine; accidental fallthrough is a classic bug. Prefer return or break in every non-grouping case, and lint for it.

Block scoping in loops

let creates a fresh binding per iteration; var hoists a single function-scoped binding. This is the difference between a working loop of closures and three closures sharing one variable:

Code example in javascript
const fns = [];
for (var i = 0; i < 3; i++) fns.push(() => i);
console.log(fns.map((f) => f())); // [3, 3, 3]

const fns2 = [];
for (let j = 0; j < 3; j++) fns2.push(() => j);
console.log(fns2.map((f) => f())); // [0, 1, 2]

The same trap appears with for...of using var. Modern code should essentially never declare loop variables with var.

Labels, breaks, and early exit

Labeled statements let you break out of nested loops — often cleaner than flag variables:

Code example in javascript
outer:
for (const row of matrix) {
  for (const cell of row) {
    if (cell < 0) break outer;
  }
}

For conditional expressions, remember evaluation order: && short-circuits left-to-right and returns the left operand when falsy; || returns the first truthy operand; ?? only skips null/undefined — the right choice for defaults when 0 or "" are valid values.

Code example in javascript
const port = config.port ?? 3000; // keeps 0, rejects only null/undefined

Control flow via exceptions

try/catch is control flow in JavaScript, not just error plumbing. throw unwinds the stack synchronously — but never across an async boundary, which is why rejections and try/catch around await are covered later in the chapters on Promises and async/await.

Choosing the right construct

SituationConstruct
Value-dependent branching with many cases`switch` with strict comparison
Default with valid falsy values`??`, not `\\`
Escaping nested loopslabeled `break`
Per-iteration closure state`let`, never `var`
Invalid state that should halt`throw`

Exercise

In the first loop above, why do all three closures report 3?

Show answer

var i is one function-scoped binding shared by all iterations. The loop finishes with i === 3, and every closure reads that same binding. With let, each iteration gets its own copy of i, captured at that step.

Exercise

config.retries || 3 breaks when retries is 0. Rewrite it.

Show answer

config.retries ?? 3 — nullish coalescing substitutes the default only for null and undefined, so a legitimate 0 is preserved.

Spotted a mistake, or code that does not work?

Report an issue

Chapter: Control Flow

0 / 2,000 characters

Plain text, for example: the second code sample does not compile. Do not include personal information.

You can't do that