JavaScript Control Flow

Control flow is the order in which statements are executed in a program. By default, JavaScript runs code from top to bottom. Conditional statements, loops, and error handling change this flow.

Program Execution Order

JavaScript executes statements sequentially by default. Each line runs after the previous one completes. Control flow statements alter this linear execution.

Sequential Execution
// Code runs top to bottom
let a = 10;
let b = 20;
let sum = a + b;
console.log("Step 1: a = " + a);
console.log("Step 2: b = " + b);
console.log("Step 3: sum = " + sum);
// Output: Step 1: a = 10
// Output: Step 2: b = 20
// Output: Step 3: sum = 30
Control Flow TypeStatementsEffect
Sequential(default)Execute line by line
Conditionalif, else, switchBranch based on condition
Loopfor, while, do...whileRepeat a block of code
Jumpbreak, continue, returnSkip to another point
Exceptiontry, catch, finally, throwHandle errors

Conditional Branching

Conditional statements create branches in your code. Only one branch executes based on the evaluated condition. This is the most common form of control flow.

Branching Flow
let hour = 14;
let greeting;

// The program takes one of three paths
if (hour < 12) {
  greeting = "Good morning";
  console.log("Took morning branch");
} else if (hour < 18) {
  greeting = "Good afternoon";
  console.log("Took afternoon branch");
} else {
  greeting = "Good evening";
  console.log("Took evening branch");
}

console.log(greeting);
// Output: Took afternoon branch
// Output: Good afternoon

// Switch branching
let action = "save";
switch (action) {
  case "save":
    console.log("Saving...");
    break;
  case "delete":
    console.log("Deleting...");
    break;
  default:
    console.log("Unknown action");
}
// Output: Saving...

Loop Flow

Loops change control flow by repeating a block of code. The loop body executes multiple times, with each pass called an iteration. break and continue modify the loop flow.

Loop Control Flow
console.log("Before loop");

for (let i = 0; i < 5; i++) {
  if (i === 2) {
    console.log("  Skipping " + i);
    continue; // Jump to next iteration
  }
  if (i === 4) {
    console.log("  Breaking at " + i);
    break; // Exit loop entirely
  }
  console.log("  Processing " + i);
}

console.log("After loop");
// Before loop
// Processing 0
// Processing 1
// Skipping 2
// Processing 3
// Breaking at 4
// After loop

try/catch Flow

Error handling with try/catch changes control flow when exceptions occur. The try block runs normally until an error is thrown, then control jumps to the catch block. The finally block always runs.

try/catch/finally Flow
function divide(a, b) {
  try {
    console.log("1. Trying division");
    if (b === 0) {
      throw new Error("Cannot divide by zero");
    }
    let result = a / b;
    console.log("2. Result: " + result);
    return result;
  } catch (error) {
    console.log("3. Error: " + error.message);
    return null;
  } finally {
    console.log("4. Finally block (always runs)");
  }
}

console.log("--- Success case ---");
divide(10, 2);
// 1. Trying division
// 2. Result: 5
// 4. Finally block (always runs)

console.log("--- Error case ---");
divide(10, 0);
// 1. Trying division
// 3. Error: Cannot divide by zero
// 4. Finally block (always runs)

return and throw

The return statement exits a function and optionally provides a value. The throw statement creates a custom error and transfers control to the nearest catch block.

return and throw Flow
// return exits the function immediately
function findFirst(arr, predicate) {
  for (let item of arr) {
    if (predicate(item)) {
      console.log("Found: " + item);
      return item; // Exits function (and loop)
    }
  }
  console.log("Not found");
  return undefined;
}

let nums = [1, 3, 5, 8, 9];
let even = findFirst(nums, n => n % 2 === 0);
console.log("Result: " + even);
// Found: 8
// Result: 8

// throw transfers control to catch
function validateAge(age) {
  if (typeof age !== "number") {
    throw new TypeError("Age must be a number");
  }
  if (age < 0 || age > 150) {
    throw new RangeError("Age must be 0-150");
  }
  return true;
}

try {
  validateAge(-5);
} catch (e) {
  console.log(e.constructor.name + ": " + e.message);
}
// RangeError: Age must be 0-150
📝 Note: The finally block runs regardless of whether an error occurred or not, and even if a return statement was encountered in the try or catch block. This makes it ideal for cleanup operations like closing connections or releasing resources.
Exercise:
In a try/catch/finally block, when does the finally block execute?
Try it YourselfCtrl+Enter to run
Click Run to see the output here.