Control Flow

Conditionals, loops, and every way to control the execution path of your JavaScript code

Last updated on

Control flow determines which code runs and how many times it runs. Mastering these patterns makes you write efficient, readable logic.

Conditionals — Making Decisions

if / else if / else

const score = 85;

if (score >= 90) {
  console.log("Grade: A");
} else if (score >= 80) {
  console.log("Grade: B"); // ← this runs
} else if (score >= 70) {
  console.log("Grade: C");
} else {
  console.log("Grade: F");
}

switch

Best when comparing one value against multiple options:

const role = "admin";

switch (role) {
  case "admin":
    console.log("Full access");
    break; // ← forgetting break causes fall-through!
  case "editor":
    console.log("Edit access");
    break;
  case "viewer":
    console.log("Read only");
    break;
  default:
    console.log("Unknown role");
}

Common mistake — forgetting break:

switch (day) {
  case "Mon":
  case "Tue":
  case "Wed":
  case "Thu":
  case "Fri":
    console.log("Weekday"); // intentional fall-through
    break;
  case "Sat":
  case "Sun":
    console.log("Weekend");
    break;
}

Ternary Operator

For simple if/else in one line:

const status = age >= 18 ? "Adult" : "Minor";

// Don't nest ternaries — use if/else instead
// ❌ Hard to read
const grade = score >= 90 ? "A" : score >= 80 ? "B" : "C";

// ✅ Clear
let grade;
if (score >= 90) grade = "A";
else if (score >= 80) grade = "B";
else grade = "C";

Loops — Repeating Actions

for Loop

The classic — use when you know how many iterations:

for (let i = 0; i < 5; i++) {
  console.log(i); // 0, 1, 2, 3, 4
}

// Counting backwards
for (let i = 10; i > 0; i--) {
  console.log(i); // 10, 9, 8, ... 1
}

while Loop

Use when you don't know how many iterations:

let attempts = 0;

while (attempts < 3) {
  const success = tryConnect();
  if (success) break;
  attempts++;
}

do...while Loop

Guarantees at least one execution:

let input;

do {
  input = prompt("Enter a number greater than 10:");
} while (Number(input) <= 10);

for...of — Iterate Over Values (ES6)

Works with arrays, strings, Maps, Sets — any iterable:

const colors = ["red", "green", "blue"];

for (const color of colors) {
  console.log(color); // "red", "green", "blue"
}

// With strings
for (const char of "hello") {
  console.log(char); // "h", "e", "l", "l", "o"
}

// With index (using entries)
for (const [index, color] of colors.entries()) {
  console.log(`${index}: ${color}`);
}

for...in — Iterate Over Keys

Works with objects (and arrays, but avoid it for arrays):

const user = { name: "Shiva", age: 25, city: "Mumbai" };

for (const key in user) {
  console.log(`${key}: ${user[key]}`);
}
// name: Shiva
// age: 25
// city: Mumbai

for...of vs for...in — Critical Difference

for...offor...in
IteratesValuesKeys (property names)
Best forArrays, Strings, Maps, SetsObjects
On arrays[1, 2, 3]1, 2, 3[1, 2, 3]"0", "1", "2"
Includes prototype?NoYes (use hasOwnProperty to filter)
// ❌ DON'T use for...in with arrays
const arr = [10, 20, 30];
for (const key in arr) {
  console.log(key);        // "0", "1", "2" (strings, not numbers!)
  console.log(typeof key); // "string"
}

// ✅ DO use for...of with arrays
for (const value of arr) {
  console.log(value); // 10, 20, 30
}

Break and Continue

break — Exit the Loop Entirely

for (let i = 0; i < 10; i++) {
  if (i === 5) break;
  console.log(i); // 0, 1, 2, 3, 4
}

continue — Skip Current Iteration

for (let i = 0; i < 10; i++) {
  if (i % 2 === 0) continue; // skip even numbers
  console.log(i); // 1, 3, 5, 7, 9
}

Labeled Statements — Breaking Nested Loops

outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) break outer; // breaks BOTH loops
    console.log(i, j);
  }
}
// 0 0, 0 1, 0 2, 1 0

Real-World Patterns

Early Return Pattern (Avoids Nesting)

// ❌ Deeply nested
function processUser(user) {
  if (user) {
    if (user.isActive) {
      if (user.hasPermission) {
        // finally do the work
        return doWork(user);
      }
    }
  }
  return null;
}

// ✅ Guard clauses with early return
function processUser(user) {
  if (!user) return null;
  if (!user.isActive) return null;
  if (!user.hasPermission) return null;

  return doWork(user);
}

Object Lookup Instead of Switch

// ❌ Verbose switch
function getStatusText(code) {
  switch (code) {
    case 200: return "OK";
    case 404: return "Not Found";
    case 500: return "Server Error";
    default: return "Unknown";
  }
}

// ✅ Clean object lookup
const STATUS_TEXT = {
  200: "OK",
  404: "Not Found",
  500: "Server Error"
};

function getStatusText(code) {
  return STATUS_TEXT[code] ?? "Unknown";
}

Processing Array Until Condition

const items = [1, 2, 3, -1, 4, 5];

// Process until we hit a negative number
const result = [];
for (const item of items) {
  if (item < 0) break;
  result.push(item * 2);
}
// result: [2, 4, 6]

Common Mistakes

1. Infinite Loop

// ❌ Forgot to update counter
let i = 0;
while (i < 5) {
  console.log(i);
  // missing i++ → runs forever
}

2. Off-by-One Error

const arr = [1, 2, 3, 4, 5];

// ❌ Uses <= instead of <
for (let i = 0; i <= arr.length; i++) {
  console.log(arr[i]); // last iteration gives undefined
}

// ✅ Correct
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

3. Modifying Array While Iterating

// ❌ Removing items during for loop — skips elements
const arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
  if (arr[i] % 2 === 0) arr.splice(i, 1);
}
// arr: [1, 3, 5] — works accidentally, but buggy approach

// ✅ Use filter instead
const odds = arr.filter(n => n % 2 !== 0);

On this page