Topics in this subject
JavaScript 2 min read Updated 4 Aug 2026

Part 8 — Control Flow

Conditionals, Loops, break, continue, labels, Iteration method comparison 🎯, Recursion

Conditionals#

if (cond) { } else if (other) { } else { }

switch (fruit) {
  case "apple":
  case "pear":            // fall-through: apple & pear share this
    console.log("pome"); break;
  default:
    console.log("other");
}

⚠️ switch uses strict comparison (===) and falls through without break. Forgetting break is a classic bug. For value-mapping, an object lookup is often cleaner:

const label = { apple: "pome", pear: "pome" }[fruit] ?? "other";

Loops#

for (let i = 0; i < n; i++) { }
while (cond) { }
do { } while (cond);        // runs body at least once

for (const x of iterable) { }   // 🟢 values (arrays, strings, Maps, Sets, generators)
for (const k in obj) { }        // ⚠️ keys, INCLUDING inherited enumerable ones

⚠️ for...in traps: iterates keys as strings, includes inherited properties, and order isn't guaranteed for integer-like keys the way you'd expect. Use it for plain objects only, guard with Object.hasOwn(obj, k), or better use Object.keys/entries. Never use for...in to iterate arrays.

break, continue, labels#

outer:
for (const row of grid) {
  for (const cell of row) {
    if (cell === target) break outer;   // break both loops
    if (cell < 0) continue outer;
  }
}

🟡 Labels are legitimate for breaking nested loops but often signal that logic should move into a function that can return.

Iteration method comparison 🎯#

Construct Iterates Can break? Returns Async-friendly Notes
for anything yes with await inside most control, fastest
for...of iterables yes ✅ (for await...of) clean, supports break/continue
for...in enumerable keys yes objects only; includes inherited
forEach array no ⚠️ undefined ❌ ignores await can't break; skips holes
map array no new array transform; don't use for side-effects only
filter array no subset array keep where callback truthy
reduce array no single value fold; powerful but can hurt readability

⚠️ You cannot break out of forEach, and await inside forEach does not pause the loop. For sequential async, use for...of with await.

// ❌ doesn't await
items.forEach(async (i) => { await save(i); });
// ✅ sequential
for (const i of items) { await save(i); }
// ✅ parallel
await Promise.all(items.map(i => save(i)));

Recursion#

ELI12. A function that calls itself, shrinking the problem each time until a "base case" stops it.

function factorial(n) {
  if (n <= 1) return 1;      // base case — always required
  return n * factorial(n - 1);
}

⚠️ No base case → infinite recursion → RangeError: Maximum call stack size exceeded. JS engines generally do not optimize tail calls (spec'd but unimplemented in V8), so deep recursion can overflow — convert to iteration or an explicit stack for large inputs.

Interview questions (Part 8):

  1. Why can't you break out of forEach? What do you use instead?
  2. for...in vs for...of?
  3. What happens with await inside forEach? Fix it two ways (sequential/parallel).