Skip to main content

How to Use the Switch Statement in JavaScript

When your code needs to compare a single value against many possible options, writing a long chain of if...else if statements becomes repetitive and hard to read. The switch statement provides a cleaner alternative. It evaluates an expression once and then matches the result against a list of cases, executing the corresponding block of code.

While switch looks straightforward, it has one behavior that catches nearly every beginner: fall-through. Forgetting a single break keyword can cause your program to execute code from multiple cases. This guide covers the switch statement thoroughly, explains exactly how fall-through works, shows you when switch is the right tool, and helps you avoid the most common mistakes.

Syntax and How It Works

Basic Structure

switch (expression) {
case value1:
// code to run if expression === value1
break;
case value2:
// code to run if expression === value2
break;
case value3:
// code to run if expression === value3
break;
default:
// code to run if no case matches
}

How It Executes

The switch statement follows this process:

  1. Evaluate the expression inside the parentheses (once)
  2. Compare the result against each case value, from top to bottom
  3. When a match is found, execute the code starting at that case
  4. Continue executing until a break statement or the end of the switch block
  5. If no case matches and a default clause exists, execute the default code

Basic Example

let day = "Tuesday";

switch (day) {
case "Monday":
console.log("Start of the work week");
break;
case "Tuesday":
console.log("Second day of the work week");
break;
case "Wednesday":
console.log("Midweek");
break;
case "Thursday":
console.log("Almost Friday");
break;
case "Friday":
console.log("TGIF!");
break;
case "Saturday":
console.log("Weekend!");
break;
case "Sunday":
console.log("Rest day");
break;
default:
console.log("Invalid day");
}
// Output: "Second day of the work week"

Step by step:

  1. day evaluates to "Tuesday"
  2. "Tuesday" === "Monday"? No, skip
  3. "Tuesday" === "Tuesday"? Yes, enter this case
  4. Execute console.log("Second day of the work week")
  5. Hit break, exit the switch

The default Clause

The default clause runs when no case matches. It is similar to the final else in an if...else if chain:

let statusCode = 418;

switch (statusCode) {
case 200:
console.log("OK");
break;
case 404:
console.log("Not Found");
break;
case 500:
console.log("Server Error");
break;
default:
console.log(`Unknown status code: ${statusCode}`);
}
// Output: "Unknown status code: 418"

The default clause is optional, but including it is good practice. It handles unexpected values and makes your code more robust.

default Does Not Have to Be Last

While default is conventionally placed at the end, it can appear anywhere in the switch block:

switch (value) {
default:
console.log("Default case");
break;
case 1:
console.log("One");
break;
case 2:
console.log("Two");
break;
}

This works because switch compares each case first. If none match, it jumps to default regardless of its position. However, placing default at the end is the universal convention, and deviating from it confuses other developers.

Expressions in switch and case

Both the switch expression and case values can be any expression, not just simple values:

let score = 85;

switch (true) {
case score >= 90:
console.log("Grade: A");
break;
case score >= 80:
console.log("Grade: B");
break;
case score >= 70:
console.log("Grade: C");
break;
default:
console.log("Grade: F");
}
// Output: "Grade: B"

In this pattern, switch(true) compares true against each case expression. The first case that evaluates to true matches. This effectively turns switch into an if...else if chain, though most developers prefer actual if...else if for this kind of logic.

// case values can also be expressions
let x = 10;
let y = 5;

switch (x + y) {
case 10 + 5:
console.log("Fifteen");
break;
case 20 - 2:
console.log("Eighteen");
break;
}
// Output: "Fifteen" ((10 + 5) === (10 + 5))

The Importance of break (Fall-Through Behavior)

This is the most critical concept to understand about switch. Without break, execution falls through to the next case, regardless of whether that case matches.

What Happens Without break

let fruit = "apple";

switch (fruit) {
case "apple":
console.log("Apple selected");
// No break! Execution continues to the next case
case "banana":
console.log("Banana selected");
// No break! Continues again
case "cherry":
console.log("Cherry selected");
// No break! Continues again
default:
console.log("Unknown fruit");
}

Output:

Apple selected
Banana selected
Cherry selected
Unknown fruit

All four messages print, even though only "apple" matched. Once a matching case is found, JavaScript executes all subsequent code until it hits a break or the end of the switch block. The other case labels are completely ignored after the initial match.

How break Fixes This

let fruit = "apple";

switch (fruit) {
case "apple":
console.log("Apple selected");
break; // Exit switch immediately
case "banana":
console.log("Banana selected");
break;
case "cherry":
console.log("Cherry selected");
break;
default:
console.log("Unknown fruit");
}
// Output: "Apple selected" (only the matching case runs)

Visualizing Fall-Through

Think of switch cases as entry points, not isolated blocks. Without break, you enter at the matching case and keep running down:

switch (value) {
case "A": ← Enter here if value === "A"
doA();
↓ Falls through (no break)
case "B":
doB();
↓ Falls through (no break)
case "C":
doC();
break; ← Exit here
case "D":
doD();
break;
}

If value === "A": doA(), doB(), doC() all execute
If value === "B": doB(), doC() execute
If value === "C": doC() executes
If value === "D": doD() executes

The default Clause and break

If default is at the end, its break is technically unnecessary because the switch block ends naturally. However, including it is recommended for consistency and to prevent bugs if someone later adds a case after default:

switch (status) {
case "active":
activate();
break;
case "inactive":
deactivate();
break;
default:
handleUnknown();
break; // Not strictly needed here, but good practice
}

Grouping Cases

Fall-through is not always a bug. Sometimes you intentionally want multiple cases to execute the same code. Grouping cases is the one legitimate use of fall-through.

Basic Case Grouping

let day = "Wednesday";

switch (day) {
case "Monday":
case "Tuesday":
case "Wednesday":
case "Thursday":
case "Friday":
console.log("Weekday");
break;
case "Saturday":
case "Sunday":
console.log("Weekend");
break;
default:
console.log("Invalid day");
}
// Output: "Weekday"

The cases "Monday" through "Friday" have no code and no break, so they all fall through to the console.log("Weekday") line. This is clean, readable, and the accepted pattern for grouping cases.

Practical Examples

Month groups:

let month = "March";

switch (month) {
case "December":
case "January":
case "February":
console.log("Winter");
break;
case "March":
case "April":
case "May":
console.log("Spring");
break;
case "June":
case "July":
case "August":
console.log("Summer");
break;
case "September":
case "October":
case "November":
console.log("Fall");
break;
default:
console.log("Invalid month");
}
// Output: "Spring"

File type handling:

let extension = ".jpg";

switch (extension) {
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
case ".webp":
console.log("Image file");
break;
case ".mp4":
case ".avi":
case ".mov":
case ".webm":
console.log("Video file");
break;
case ".mp3":
case ".wav":
case ".flac":
console.log("Audio file");
break;
case ".pdf":
case ".doc":
case ".docx":
case ".txt":
console.log("Document");
break;
default:
console.log("Unknown file type");
}
// Output: "Image file"

Permission levels with cumulative access:

This is a rare case where intentional fall-through with code in each case makes sense:

let role = "editor";

let permissions = [];

switch (role) {
case "admin":
permissions.push("manage users");
// Intentional fall-through: admins also get editor permissions
case "editor":
permissions.push("edit content");
// Intentional fall-through: editors also get viewer permissions
case "viewer":
permissions.push("view content");
break;
default:
permissions.push("no access");
}

console.log(permissions);
// ["edit content", "view content"]
// Editor gets their own permission plus all permissions below
caution

When you intentionally use fall-through with code in a case, always add a comment explaining it:

case "admin":
permissions.push("manage users");
// falls through intentionally
case "editor":
permissions.push("edit content");
// falls through intentionally
case "viewer":
permissions.push("view content");
break;

Many linters (ESLint's no-fallthrough rule) will flag uncommented fall-through as an error. The comment // falls through is the recognized way to signal that the fall-through is deliberate.

switch Uses Strict Comparison (===)

The switch statement uses strict equality (===) to compare the expression with each case value. No type coercion occurs. This is an important detail that prevents subtle bugs.

Type Must Match Exactly

let value = "1";

switch (value) {
case 1:
console.log("Number 1");
break;
case "1":
console.log("String '1'");
break;
}
// Output: "String '1'"
// The string "1" does NOT match the number 1

Common Trap with prompt()

Since prompt() always returns a string (or null), comparing against numbers will fail:

let input = prompt("Enter a number (1-3):");  // Returns "2" (string)

// BUG: cases are numbers, but input is a string
switch (input) {
case 1:
console.log("One");
break;
case 2:
console.log("Two");
break;
case 3:
console.log("Three");
break;
default:
console.log("No match!"); // This always runs!
}

// FIX: either convert the input or use string cases
// Option 1: Convert input
switch (Number(input)) {
case 1:
console.log("One");
break;
case 2:
console.log("Two");
break;
case 3:
console.log("Three");
break;
}

// Option 2: Use string cases
switch (input) {
case "1":
console.log("One");
break;
case "2":
console.log("Two");
break;
case "3":
console.log("Three");
break;
}

Other Type Mismatches

let active = true;

switch (active) {
case 1:
console.log("This does NOT match"); // 1 !== true with ===
break;
case true:
console.log("This matches"); // true === true
break;
}
// Output: "This matches"
let data = null;

switch (data) {
case undefined:
console.log("Undefined"); // null !== undefined with ===
break;
case null:
console.log("Null"); // null === null
break;
case false:
console.log("False"); // null !== false
break;
}
// Output: "Null"
info

The strict comparison behavior of switch is actually an advantage. It prevents the type coercion surprises you would get with ==. Just remember that both the switch expression and case values must be the same type for a match.

switch vs. if...else if: When to Use Which

Both switch and if...else if can handle multiple branches. Choosing between them depends on what you are comparing.

When switch is Better

Comparing a single value against multiple known constants:

// switch is cleaner here
let action = "save";

switch (action) {
case "save":
saveDocument();
break;
case "load":
loadDocument();
break;
case "delete":
deleteDocument();
break;
case "export":
exportDocument();
break;
case "print":
printDocument();
break;
default:
console.log(`Unknown action: ${action}`);
}

// Equivalent if...else if is more repetitive
if (action === "save") {
saveDocument();
} else if (action === "load") {
loadDocument();
} else if (action === "delete") {
deleteDocument();
} else if (action === "export") {
exportDocument();
} else if (action === "print") {
printDocument();
} else {
console.log(`Unknown action: ${action}`);
}

With switch, the variable being tested (action) appears once. With if...else if, it is repeated in every condition, creating more noise and more chances for typos.

When if...else if Is Better

Range comparisons and complex conditions:

// if...else if is better for ranges
let score = 85;

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

// switch for ranges is awkward and less readable
switch (true) {
case score >= 90:
console.log("A");
break;
case score >= 80:
console.log("B");
break;
case score >= 70:
console.log("C");
break;
default:
console.log("F");
}

Multiple different variables in conditions:

// if...else if handles multiple variables naturally
if (age >= 18 && hasLicense) {
console.log("Can drive");
} else if (age >= 16 && hasPermit) {
console.log("Can drive with supervision");
} else {
console.log("Cannot drive");
}

// switch cannot do this cleanly

Boolean expressions and negative checks:

if (!user) {
redirectToLogin();
} else if (user.isBanned) {
showBanMessage();
} else if (!user.hasVerifiedEmail) {
showVerificationPrompt();
} else {
showDashboard();
}

Decision Guide

ScenarioUse
One variable compared against many specific valuesswitch
Grouping multiple values for the same actionswitch (case grouping)
Range comparisons (>, <, >=, <=)if...else if
Complex conditions with multiple variablesif...else if
Boolean expressions (!, &&, ||)if...else if
Two outcomes onlyif...else
Inline value assignmentTernary operator

The Object Lookup Alternative

For simple value-to-value mappings, an object lookup is often cleaner than both switch and if...else if:

// Instead of a switch...
let dayNumber = 3;
let dayName;

switch (dayNumber) {
case 1: dayName = "Monday"; break;
case 2: dayName = "Tuesday"; break;
case 3: dayName = "Wednesday"; break;
case 4: dayName = "Thursday"; break;
case 5: dayName = "Friday"; break;
case 6: dayName = "Saturday"; break;
case 7: dayName = "Sunday"; break;
default: dayName = "Invalid";
}

// ...use an object lookup
const dayNames = {
1: "Monday",
2: "Tuesday",
3: "Wednesday",
4: "Thursday",
5: "Friday",
6: "Saturday",
7: "Sunday",
};

let dayName = dayNames[dayNumber] ?? "Invalid";
console.log(dayName); // "Wednesday"
// Object lookup for function dispatch
const handlers = {
save: saveDocument,
load: loadDocument,
delete: deleteDocument,
export: exportDocument,
};

let action = "save";
let handler = handlers[action];

if (handler) {
handler();
} else {
console.log(`Unknown action: ${action}`);
}

Object lookups are more concise, do not require break statements, and are easier to modify (just add a property to the object). They are especially useful when the mapping is data-driven or loaded from a configuration.

Common Mistake: Forgetting break and Unintended Fall-Through

This is the number one switch bug. It is so common that ESLint has a dedicated rule (no-fallthrough) to catch it.

The Classic Bug

let color = "red";

switch (color) {
case "red":
console.log("Stop");
case "yellow":
console.log("Caution");
case "green":
console.log("Go");
}

Expected output:

Stop

Actual output:

Stop
Caution
Go

The developer expected only "Stop" to print, but all three messages appear because there are no break statements.

A More Dangerous Version

let userRole = "viewer";

switch (userRole) {
case "viewer":
grantReadAccess();
case "editor":
grantWriteAccess(); // Viewers should NOT get write access!
case "admin":
grantAdminAccess(); // Viewers should DEFINITELY NOT get admin access!
break;
}
// A viewer just got admin access due to missing break statements

How to Prevent This Mistake

1. Always add break immediately after writing the case body:

// Good habit: write break FIRST, then fill in the body
switch (role) {
case "viewer":
// TODO: add code
break;
case "editor":
// TODO: add code
break;
case "admin":
// TODO: add code
break;
}

2. Enable ESLint's no-fallthrough rule:

{
"rules": {
"no-fallthrough": "error"
}
}

This flags any case that does not end with break, return, throw, or a // falls through comment.

3. Use return instead of break when inside a function:

function getStatusMessage(code) {
switch (code) {
case 200:
return "OK"; // return exits the function AND the switch
case 404:
return "Not Found";
case 500:
return "Server Error";
default:
return "Unknown";
}
}

console.log(getStatusMessage(404)); // "Not Found"

Using return eliminates the need for break entirely and removes any possibility of fall-through. This is the cleanest pattern when switch is inside a function.

Variables and Block Scope in switch

Another subtle issue: all case clauses share the same scope. Declaring a variable with let in one case and accessing it in another can cause errors:

// BUG: redeclaration error
switch (action) {
case "save":
let message = "Saved!";
console.log(message);
break;
case "delete":
let message = "Deleted!"; // SyntaxError: Identifier 'message' already declared
console.log(message);
break;
}

Fix: Wrap each case in its own block with curly braces:

switch (action) {
case "save": {
let message = "Saved!";
console.log(message);
break;
}
case "delete": {
let message = "Deleted!"; // No error (different block scope)
console.log(message);
break;
}
}

Adding { } around each case creates a separate block scope, allowing let and const declarations without conflicts. This is a common pattern in larger switch statements.

Summary

The switch statement provides a structured way to handle multiple branches based on a single value:

  • switch evaluates an expression once and matches it against case values using strict equality (===). No type coercion occurs, so the type must match exactly.
  • The default clause handles unmatched values, similar to a final else. Place it at the end for convention.
  • break is essential. Without it, execution falls through to subsequent cases regardless of whether they match. Always add break at the end of each case.
  • Case grouping is the one legitimate use of fall-through. Stack empty cases to run the same code for multiple values.
  • When intentional fall-through includes code in a case, add a // falls through comment to communicate intent and satisfy linters.
  • Use switch when comparing one variable against many specific constant values. Use if...else if for ranges, complex conditions, and multiple variables.
  • Object lookups are a cleaner alternative to switch for simple value-to-value mappings.
  • Using return instead of break inside functions eliminates fall-through risk entirely.
  • Wrap case bodies in { } blocks when declaring variables with let or const to avoid scope conflicts.

With switch and conditional branching mastered, you are ready to learn about functions, which let you organize your code into reusable, named blocks of logic.