Episode 1 — Fundamentals / 1.19 — Conditionals and Loops

1.19.b -- Switch Statement

In one sentence: The switch statement compares one expression against many case values using strict equality (===), executing the matched branch until a break (or the end), with an optional default fallback for unmatched values.

Navigation: <-- 1.19.a -- If/Else Branching . 1.19.c -- For Loop -->


1. Basic switch syntax

switch (expression) {
  case value1:
    // code if expression === value1
    break;
  case value2:
    // code if expression === value2
    break;
  default:
    // code if no case matched
}

How it works step by step:

  1. Evaluate expression once.
  2. Compare the result to each case value using === (strict equality -- no type coercion).
  3. If a match is found, execute code from that case downward until a break or the end of the switch.
  4. If no case matches and a default exists, execute the default block.
const day = "Tuesday";

switch (day) {
  case "Monday":
    console.log("Start of the work week.");
    break;
  case "Tuesday":
    console.log("Second day.");   // <-- this runs
    break;
  case "Friday":
    console.log("Almost weekend!");
    break;
  default:
    console.log("Just another day.");
}
// Output: "Second day."

2. The default case

The default case handles any value not matched by a case. It is optional but strongly recommended.

function getStatusText(code) {
  switch (code) {
    case 200: return "OK";
    case 301: return "Moved Permanently";
    case 404: return "Not Found";
    case 500: return "Internal Server Error";
    default:  return `Unknown status: ${code}`;
  }
}

console.log(getStatusText(200));  // "OK"
console.log(getStatusText(418));  // "Unknown status: 418"

Placement: default is usually last, but it can appear anywhere. The spec allows it -- but putting it last is the universal convention.


3. Fall-through behavior

When a case matches and there is no break, execution falls through into the next case's code. This is the most common source of bugs -- and occasionally an intentional feature.

Accidental fall-through (bug)

const fruit = "apple";

switch (fruit) {
  case "apple":
    console.log("You picked an apple.");
    // BUG: missing break!
  case "banana":
    console.log("You picked a banana.");
    break;
  case "cherry":
    console.log("You picked a cherry.");
    break;
}
// Output:
// "You picked an apple."
// "You picked a banana."   <-- unintended!

Intentional fall-through (grouping)

const day = "Saturday";

switch (day) {
  case "Monday":
  case "Tuesday":
  case "Wednesday":
  case "Thursday":
  case "Friday":
    console.log("Weekday -- time to work.");
    break;
  case "Saturday":
  case "Sunday":
    console.log("Weekend -- time to rest.");
    break;
}
// Output: "Weekend -- time to rest."

When using intentional fall-through, add a comment so other developers know it is deliberate:

case "Saturday":  // fall-through intentional
case "Sunday":
  console.log("Weekend");
  break;

4. Grouping multiple cases

Grouping is the most practical use of fall-through. Stack cases that share the same handler:

function getQuarter(month) {
  switch (month) {
    case 1: case 2: case 3:
      return "Q1";
    case 4: case 5: case 6:
      return "Q2";
    case 7: case 8: case 9:
      return "Q3";
    case 10: case 11: case 12:
      return "Q4";
    default:
      return "Invalid month";
  }
}

console.log(getQuarter(5));   // "Q2"
console.log(getQuarter(11));  // "Q4"

5. switch vs if-else -- when to prefer each

CriteriaPrefer switchPrefer if-else
Comparison typeSingle expression against many discrete valuesRange checks, complex boolean logic
ReadabilityCleaner when 3+ exact matchesBetter for >=, <, &&, || conditions
EqualityAlways === (strict)You choose the operator
Action on matchSame structure per caseDifferent shapes of logic per branch
GroupingEasy via fall-throughRequires || chains
// switch is ideal here -- many exact matches
switch (command) {
  case "start": startGame(); break;
  case "pause": pauseGame(); break;
  case "reset": resetGame(); break;
  default: console.log("Unknown command");
}

// if-else is better here -- range logic
if (temp > 35) {
  console.log("Hot");
} else if (temp > 20) {
  console.log("Warm");
} else {
  console.log("Cold");
}

6. Using switch with different types

Strings

function getColorHex(color) {
  switch (color.toLowerCase()) {
    case "red":   return "#FF0000";
    case "green": return "#00FF00";
    case "blue":  return "#0000FF";
    default:      return null;
  }
}

Numbers

function diceRoll(value) {
  switch (value) {
    case 1: return "one";
    case 2: return "two";
    case 3: return "three";
    case 4: return "four";
    case 5: return "five";
    case 6: return "six";
    default: return "invalid die face";
  }
}

Expressions in case (advanced)

The case clause can hold expressions, not just literals. The switch evaluates them.

const x = 15;

switch (true) {                // compare each case to `true`
  case x < 10:
    console.log("Small");
    break;
  case x < 20:
    console.log("Medium");     // runs -- (15 < 20) === true
    break;
  case x < 30:
    console.log("Large");
    break;
}

This switch (true) pattern mimics if-else chains inside a switch. It works but is generally less clear than plain if-else.


7. Common mistake: forgetting break

This is the number-one switch bug. Linters like ESLint have the no-fallthrough rule to catch it.

// BUG
function getLabel(role) {
  let label;
  switch (role) {
    case "admin":
      label = "Administrator";
      // missing break -- falls through
    case "editor":
      label = "Editor";          // overwrites "Administrator"!
      break;
    case "viewer":
      label = "Viewer";
      break;
  }
  return label;
}

console.log(getLabel("admin"));  // "Editor" -- WRONG

Fix: Always include break (or return) at the end of each case block.

function getLabel(role) {
  switch (role) {
    case "admin":  return "Administrator";
    case "editor": return "Editor";
    case "viewer": return "Viewer";
    default:       return "Unknown";
  }
}

Using return instead of break inside functions is often cleaner -- you exit the function immediately.


8. Block scoping inside cases

If you declare variables with let or const inside a case, wrap the case body in { } to create a block scope. Otherwise, two cases sharing the same switch scope will conflict.

switch (action) {
  case "create": {
    const item = buildItem(data);   // scoped to this block
    save(item);
    break;
  }
  case "delete": {
    const item = findItem(id);      // no conflict with the above
    remove(item);
    break;
  }
}

Without the braces, const item in the first case would conflict with const item in the second case (same block scope = SyntaxError).


9. Real-world examples

Example 1 -- HTTP status codes

function handleResponse(status) {
  switch (status) {
    case 200:
    case 201:
      console.log("Success!");
      break;
    case 301:
    case 302:
      console.log("Redirecting...");
      break;
    case 400:
      console.log("Bad request. Check your input.");
      break;
    case 401:
    case 403:
      console.log("Authentication / authorization error.");
      break;
    case 404:
      console.log("Resource not found.");
      break;
    case 500:
      console.log("Server error. Try again later.");
      break;
    default:
      console.log(`Unhandled status code: ${status}`);
  }
}

Example 2 -- Menu selection

function executeMenuChoice(choice) {
  switch (choice) {
    case 1:
      console.log("Opening new file...");
      break;
    case 2:
      console.log("Saving current file...");
      break;
    case 3:
      console.log("Printing...");
      break;
    case 4:
      console.log("Exiting application.");
      break;
    default:
      console.log("Invalid choice. Please select 1-4.");
  }
}

Example 3 -- Day type classifier

function classifyDay(day) {
  switch (day.toLowerCase()) {
    case "monday":
      return { type: "weekday", energy: "low", tip: "Ease into the week." };
    case "tuesday":
    case "wednesday":
    case "thursday":
      return { type: "weekday", energy: "medium", tip: "Deep work hours." };
    case "friday":
      return { type: "weekday", energy: "winding down", tip: "Wrap up loose ends." };
    case "saturday":
    case "sunday":
      return { type: "weekend", energy: "recharge", tip: "Rest and hobbies." };
    default:
      throw new Error(`Unknown day: ${day}`);
  }
}

Key takeaways

  1. switch compares with === -- no type coercion.
  2. break (or return) is required after each case to prevent fall-through.
  3. Intentional fall-through is useful for grouping cases with the same handler -- add a comment.
  4. Use switch when comparing one expression against many discrete values; use if-else for ranges or complex logic.
  5. Wrap case bodies in { } when declaring let/const variables.

Explain-It Challenge

Explain without notes:

  1. What happens if you omit break from a matching case?
  2. Does switch use == or ===? Why does this matter for switch ("1") { case 1: ... }?
  3. Rewrite a three-case switch as an equivalent if-else chain.

Navigation: <-- 1.19.a -- If/Else Branching . 1.19.c -- For Loop -->