What you'll be able to do

By the end of this page you should be able to run one block of code when a condition is true and another when it is not, pick a message from a small fixed list with switch, and flatten nested if chains so a form handler stays readable. You will also know when else if is enough and when switch is the calmer shape.

Previous post: optional chaining and nullish coalescing.

Who this is for

  • People who wrote three nested if blocks and lost track of which else belonged to which if
  • Beginners who need “show error if empty, otherwise show success” on a button click
  • Anyone who copied a long else if chain and wondered if switch would be cleaner

You can skip this if you already write flat if/else if with early returns, you use === in conditions, and you know switch needs break. Come back when a form shows the wrong message or your indentation looks like a staircase.

if and else: two paths, one decision

An if runs a block when the condition is truthy (see the truthy post). else runs when it is not.

const age = 17;

if (age >= 18) {
  console.log("Adult layout.");
} else {
  console.log("Teen layout.");
}

Scenario A — a gate on a number. age >= 18 is a clear yes/no. Use >= when the boundary value should pass.

Scenario B — a form with a name. After const name = input.value.trim();, you often want:

if (name.length === 0) {
  message.textContent = "Type a name first.";
} else {
  message.textContent = `Hello, ${name}.`;
}

That is the same shape: one failure path, one happy path. Check the failure first so the happy path is not buried inside another if.

else if for more than two outcomes

When you have three or more fixed answers, chain else if:

const score = 72;

if (score >= 90) {
  console.log("A");
} else if (score >= 75) {
  console.log("B");
} else if (score >= 60) {
  console.log("C");
} else {
  console.log("Try again");
}

Scenario A — letter grades. Each branch is a range. Order matters: check the highest band first, or a 95 might stop at the wrong else if.

Scenario B — a status string from an API. "loading", "ready", "error" — compare with ===:

if (status === "loading") {
  showSpinner();
} else if (status === "ready") {
  showContent();
} else if (status === "error") {
  showError();
} else {
  showError();
}

If you have more than three or four string comparisons on the same variable, switch often reads better.

switch: one value, many fixed cases

switch compares one expression to several case labels. It is a good fit when the variable can only be a small set of known strings or numbers.

const plan = "free";

switch (plan) {
  case "free":
    console.log("Up to 3 exports.");
    break;
  case "pro":
    console.log("Unlimited exports.");
    break;
  case "team":
    console.log("Shared workspace.");
    break;
  default:
    console.log("Unknown plan.");
}

Scenario A — menu actions. User picks “save”, “export”, or “delete” from a dropdown. Each case runs one handler. Without break, execution falls through to the next case, which is rarely what you wanted on a first try.

Scenario B — weekday label. switch (day) with case 0: through case 6: is readable. Do not forget default for unexpected values so you have one place to log “we got something weird.”

switch uses strict matching (like ===), not loose ==. case 5: does not match the string "5".

You cannot switch on arbitrary ranges cleanly (score >= 90). For ranges, stick with if / else if. For one variable with many exact values, switch shines.

Flatten nested if spaghetti

Nested if inside if inside if is hard to read and easy to break.

// Hard to follow
if (loggedIn) {
  if (hasName) {
    if (age >= 18) {
      showDashboard();
    }
  }
}

Scenario A — early return (or early exit). Handle the boring failures first and leave:

if (!loggedIn) {
  showLogin();
  return;
}
if (!hasName) {
  showNameForm();
  return;
}
if (age < 18) {
  showTeenView();
  return;
}
showDashboard();

Each guard is one screen tall. You can read top to bottom like steps.

Scenario B — combine related checks. If two conditions always belong together, use &&:

if (loggedIn && hasName && age >= 18) {
  showDashboard();
} else if (loggedIn && hasName) {
  showTeenView();
} else if (loggedIn) {
  showNameForm();
} else {
  showLogin();
}

That is still one level of nesting. Prefer early returns in click handlers; save big else if ladders for when you truly need one expression at the end.

Blocks, braces, and one-liner if

Always use { } around multi-line blocks. For a single statement, braces are optional but safer:

if (ready) start();

If you add a second line later without braces, only the first line stays inside the if:

if (ready)
  start();
  log("started"); // runs always — surprise

Scenario A — quick guard. if (!name) return; on one line is fine in handlers.

Scenario B — anything you will edit again. Use braces so the next person (you in a week) does not break the branch.

A tiny practice file

Save as branch.html. Pick a plan from the dropdown and click the button.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Branch</title>
  </head>
  <body>
    <select id="plan">
      <option value="free">free</option>
      <option value="pro">pro</option>
      <option value="team">team</option>
    </select>
    <button id="go">Show limit</button>
    <p id="out">Message will show here.</p>
    <script>
      const planSelect = document.getElementById("plan");
      const goButton = document.getElementById("go");
      const out = document.getElementById("out");

      goButton.addEventListener("click", function () {
        const plan = planSelect.value;
        let message;

        switch (plan) {
          case "free":
            message = "Up to 3 exports per day.";
            break;
          case "pro":
            message = "Unlimited exports.";
            break;
          case "team":
            message = "Shared workspace and seats.";
            break;
          default:
            message = "Pick a plan.";
        }

        out.textContent = message;
      });
    </script>
  </body>
</html>

Try removing one break on purpose and watch two messages stack in your head — that is fall-through. Put the break back before you forget.

Mistakes I see a lot

1. Using = instead of === in the condition. if (status = "ready") assigns. Use ===.

2. Checking ranges in the wrong order. if (score >= 60) before if (score >= 90) catches everyone at C and never reaches A.

3. Forgetting break in switch. The next case runs too.

4. Deep nesting instead of early return. Three indented if blocks when four flat guards would read faster.

5. switch on a boolean or a range. Use if/else for isLoggedIn and for score >= 90.

6. No default when the input can be unexpected. API typos and old cached values happen.

What to try before the next post

  1. Write an if / else for name.length === 0 vs a greeting.
  2. Rewrite a nested triple if with three early returns.
  3. Log a switch with one break removed so you see fall-through once.
  4. Build the tiny HTML file and switch plans.

Next in this series: loops — for, while, for…of, and for…in — including when each one fits arrays vs objects.

Try this next outside the series

Control flow starts to stick when it decides real UI and request outcomes.