Switch Statements and Ternary Operators

7 min read

The if chain is JavaScript's general-purpose decision tool, but two alternatives express specific shapes more cleanly: switch for dispatching one variable to many discrete values, and the ternary ? : for one-line "this or that" expressions. This lesson teaches both, the traps each one carries, and when to fall back on a plain if/else.

switch — one variable, many cases

When you're checking the same variable against several specific values, a long if/else if chain reads as visual noise:

if (method === "GET")          runReadTest();
else if (method === "POST")    runCreateTest();
else if (method === "PUT")     runUpdateTest();
else if (method === "DELETE")  runDeleteTest();
else                           throw new Error("Unknown method");

switch is the same logic, but the branching variable is named once at the top:

const method = "POST";
 
switch (method) {
  case "GET":
    console.log("Run read test");
    break;
  case "POST":
    console.log("Run create test");
    break;
  case "PUT":
    console.log("Run update test");
    break;
  case "DELETE":
    console.log("Run delete test");
    break;
  default:
    console.log("Unknown method");
}

Output:

Run create test

Each piece does one job. switch (method) says "compare method against the cases below." case "POST": is the value to match. The block runs until break. default runs if no case matched — the equivalent of the trailing else.

switch uses strict equality (===) under the hood, so the type-coercion warnings from the previous lesson don't apply here.

The break trap

The single most common switch bug is forgetting break. Without it, JavaScript "falls through" — once a case matches, every block below it runs too, until it hits a break or the end.

Buggy:

const severity = "high";
 
switch (severity) {
  case "critical":
    console.log("Page on-call");
  case "high":
    console.log("Slack the team");
  case "medium":
    console.log("Email the lead");
  case "low":
    console.log("Add to dashboard");
}

Output (with no breaks):

Slack the team
Email the lead
Add to dashboard

Three escalations for one bug. Adding break after each case fixes it:

switch (severity) {
  case "critical":
    console.log("Page on-call");
    break;
  case "high":
    console.log("Slack the team");
    break;
  case "medium":
    console.log("Email the lead");
    break;
  case "low":
    console.log("Add to dashboard");
    break;
}

Output:

Slack the team

Fall-through can be intentional — multiple cases sharing the same body — but use it with a comment so the next reader knows it wasn't an oversight:

switch (statusCode) {
  case 200:
  case 201:
  case 204:
    // intentional fall-through — all 2xx success codes share one branch
    console.log("✅ success");
    break;
  default:
    console.log("❌ failure");
}

The ternary operator

When a decision boils down to "this value if true, that value if false," the ternary ? : operator condenses an entire if/else into one expression.

Syntax:

condition ? valueIfTrue : valueIfFalse

Real example:

const passed = true;
const result = passed ? "✅ PASS" : "❌ FAIL";
console.log(result);

Output:

✅ PASS

The ternary is an expression — it evaluates to a value. That's its main superpower over if/else: you can use it on the right-hand side of an assignment, inside a template literal, as a function argument. Picking a base URL based on environment is the canonical case:

const isProduction = false;
const baseUrl = isProduction
  ? "https://api.prod.com"
  : "https://api.staging.com";
 
console.log(`Running tests against ${baseUrl}`);

Output:

Running tests against https://api.staging.com

Don't nest ternaries

Ternaries shine for simple two-way picks. The temptation, once you know about them, is to chain them for three- or four-way decisions:

// Don't do this
const label =
  statusCode < 300 ? "ok"
  : statusCode < 400 ? "redirect"
  : statusCode < 500 ? "client error"
  : "server error";

That works. It also takes longer to read than an if/else chain, hides the structure, and is hostile to a debugger or breakpoint. Once you need more than one branch, switch to if/else if (or switch):

let label;
if (statusCode < 300)      label = "ok";
else if (statusCode < 400) label = "redirect";
else if (statusCode < 500) label = "client error";
else                       label = "server error";

Same logic, far easier to add a new branch to in six months without rewriting the whole expression.

Picking the right tool

if/else vs switch vs ternary — when to use each

if / else

  • Default tool — use when nothing else fits

  • Multiple distinct conditions

  • Different variables in each branch

  • Unbounded ranges (>= 200, < 300)

switch

  • One variable matched against many values

  • Clearer than 5+ else if branches

  • Watch out for missing break

  • Strict equality only

ternary ? :

  • One-line picks: const x = cond ? a : b

  • Inside template literals, JSX, args

  • Never nest more than once

  • Two outcomes only

⚠️ Common mistakes

  • Forgetting break. The fall-through bug is silent — the script runs, prints multiple lines, and looks plausible. Get into the habit of adding break immediately after writing each case, before the body. Linters (ESLint's no-fallthrough rule) catch most cases automatically.
  • Using ternary for branches that need statements. cond ? doA() : doB() is technically legal, but you're calling functions for their side effects, not their return value. That reads worse than if (cond) doA(); else doB();. Reserve the ternary for when you actually want the value.
  • Nested ternaries. Once. Maybe. Never twice. If you find yourself chaining ?: two or more times, the resulting code looks like a ransom note. Use if/else and your future self will thank you.

🎯 Practice task

Build a notification router. 15-20 minutes.

  1. In your js-for-qa folder, create notify.js.

  2. Declare const severity = "high";.

  3. Use a switch statement to print:

    • "critical""Page on-call"
    • "high""Slack the team"
    • "medium""Email the lead"
    • "low""Add to dashboard"
    • any other → "Unknown severity"
  4. Add break to each case. Run with node notify.js for several severities and confirm only one line prints each time.

  5. Below the switch, add one line using the ternary operator:

    const channel = severity === "critical" ? "pagerduty" : "slack";
    console.log(`Channel: ${channel}`);
  6. Stretch: deliberately remove the break after the "high" case. Run again — observe which lines now print. Write a one-line comment in the file explaining what fall-through is and why you should have caught this on review.

The next lesson covers loops — running the same code over many test cases, retrying on failure, processing arrays of API results.

// tip to track lessons you complete and pick up where you left off across devices.