webdev.complete
💛 JavaScript Foundations
JavaScript
Lesson 26 of 117
20 min

Control Flow

if/else, ternaries, switch, for, for-of, while.

Programs aren't just lists of instructions. They make decisions, repeat things, and exit early when something is wrong. That's control flow. JS gives you a handful of tools, and most pros use a small subset of them really well.

if / else if / else

The classic. Branch on a condition:

js
const score = 73;

if (score >= 90) {
  console.log("A");
} else if (score >= 80) {
  console.log("B");
} else if (score >= 70) {
  console.log("C");
} else {
  console.log("F");
}

Curly braces are optional for single statements, but use them anyway. Bracket-less ifs are how bugs sneak in during a midnight edit.

The ternary: short conditional values

When you just need to pick a value, not run a block, the ternary is cleaner:

js
const age = 19;
const label = age >= 18 ? "adult" : "minor";

// Nest sparingly - it gets unreadable fast
const grade =
  score >= 90 ? "A" :
  score >= 80 ? "B" :
  score >= 70 ? "C" : "F";
Ternary vs if
Use a ternary when you're assigning one value. Use if when you're running code with side effects.

switch (and when to skip it)

switchcompares one value against many cases. It's also where the most beginner JS bugs live, because forgetting a breakcauses "fall-through":

js
const role = "admin";

switch (role) {
  case "admin":
    console.log("Full access");
    break;       // forget this and the next case also runs
  case "editor":
    console.log("Edit access");
    break;
  case "viewer":
    console.log("Read only");
    break;
  default:
    console.log("No access");
}

Most of the time, a plain object lookup is shorter, faster, and impossible to break:

js
const access = {
  admin: "Full access",
  editor: "Edit access",
  viewer: "Read only",
};

console.log(access[role] ?? "No access");
Reach for switch only when you genuinely need fall-through behavior, or when each branch is many lines of code. Otherwise an object map wins.

Loops: for, for-of, for-in

JS has three numeric loops you'll see. Use them differently:

for (classic index loop)

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

Use it when you need the index, when you're counting backwards, or when you might break out early. Otherwise for-of is almost always nicer.

for-of (loop over values)

js
const items = ["a", "b", "c"];

for (const item of items) {
  console.log(item);  // "a", "b", "c"
}

// Need the index too? Use entries()
for (const [i, item] of items.entries()) {
  console.log(i, item);
}

for-in (loop over keys)

for-in is for objects, not arrays
for-in iterates keys, including inherited ones, and gives them as strings. Never use it on arrays. Use for-of for arrays and Object.keys() + for-of for objects.
js
const user = { name: "Ada", age: 36 };

for (const key in user) {
  console.log(key, user[key]);
}

// Safer modern style:
for (const [key, value] of Object.entries(user)) {
  console.log(key, value);
}

while / do-while

js
// while: check first, then run
let n = 3;
while (n > 0) {
  console.log(n);
  n--;
}

// do-while: run first, then check (runs at least once)
let answer;
do {
  answer = prompt("Pick a color");
} while (!answer);

break and continue

js
for (const n of [1, 2, 3, 4, 5]) {
  if (n === 3) continue;  // skip this iteration
  if (n === 5) break;     // exit the loop entirely
  console.log(n);  // 1, 2, 4
}

Early returns: the guard clause

Nested ifs pile up fast. The cleanest fix is to return early when something invalid happens. Your "happy path" stays unindented and readable:

js
// The nested version
function greet(user) {
  if (user) {
    if (user.name) {
      if (user.active) {
        return "Hello, " + user.name;
      }
    }
  }
  return "Hi, stranger";
}

// The guard-clause version
function greet(user) {
  if (!user) return "Hi, stranger";
  if (!user.name) return "Hi, stranger";
  if (!user.active) return "Hi, stranger";

  return "Hello, " + user.name;
}

Build a grader

Edit the function below to handle edge cases. What if the score is negative? Over 100? Not a number at all?

function grade(score) {
  // Guard clauses first
  if (typeof score !== "number" || Number.isNaN(score)) {
    return "Invalid";
  }
  if (score < 0 || score > 100) {
    return "Out of range";
  }

  // Lookup-style ladder beats a switch
  if (score >= 90) return "A";
  if (score >= 80) return "B";
  if (score >= 70) return "C";
  if (score >= 60) return "D";
  return "F";
}

// Try a bunch of scores
const tests = [95, 82, 73, 60, 42, 100, -5, 150, "abc", NaN];

for (const s of tests) {
  console.log(s, "=>", grade(s));
}

// Challenge: change the function to also return "A+" for 97+
// and "F-" for under 30.

Quiz

Quiz1 / 3

Which loop should you use to iterate the values of an array?

Recap

  • if for branching code, ternary for picking a value.
  • Object lookups usually beat switch. Use switch when you need fall-through or large blocks.
  • for-of for array values, for-in only for object keys (cautiously), for when you need the index.
  • break exits a loop, continue skips one iteration.
  • Guard clauses keep your happy path flat. Return early and often.