The switch statement is the right tool when a single variable or expression needs to be compared against multiple specific values. While a chain of else if statements can do the same job, switch is cleaner and more expressive when you have three or more distinct cases. In this lesson you will master switch syntax, the critical role of break, fall-through behaviour, and the modern object-map pattern that often replaces switch entirely.
switch vs if/else if
| Feature | switch | else if chain |
|---|---|---|
| Best for | One variable vs many fixed values | Complex, range-based, or mixed conditions |
| Comparison type | Strict equality (===) only | Any boolean expression |
| Fall-through | Yes โ cases run into the next without break | No โ only one branch runs |
| Default case | default: block |
Final else block |
| Readability at 5+ cases | Better โ clear tabular structure | Worse โ long chain of else ifs |
switch Anatomy
| Part | Purpose | Required? |
|---|---|---|
switch (expression) |
Evaluates expression once; compares to each case | Yes |
case value: |
Matches when expression === value | At least one |
break; |
Exits the switch block โ prevents fall-through | Almost always |
default: |
Runs when no case matches โ like else | Recommended |
| Fall-through (intentional) | Omit break to let multiple cases share one block | Use with care + comment |
Modern Alternatives to switch
| Pattern | Syntax | Best For |
|---|---|---|
| Object map | const map = { key: value }; map[input] ?? default |
Mapping values to results โ cleanest pattern |
| Map object | new Map([[key, value]]) |
Non-string keys or complex values |
| switch (classic) | switch (x) { case 'a': ... } |
Multiple cases sharing one block (fall-through) |
switch uses strict equality (===) for comparisons โ just like you should be using in your if statements. This means switch will NOT match a string '1' to a number 1. Always make sure the type of your switch expression matches the type of your case values.const labels = { en: 'English', fr: 'French', de: 'German' }; const label = labels[lang] ?? 'Unknown';. This is more concise, easily extensible (just add a key), and can be stored in a config file or fetched from an API.break is one of the most common JavaScript bugs. Without it, execution falls through to the next case and runs it too โ even if that case’s value did not match. Always add break at the end of every case unless you have intentionally written a fall-through with a comment explaining why.Basic Example
// โโ Basic switch โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function getDayName(dayNumber) {
switch (dayNumber) {
case 0: return 'Sunday';
case 1: return 'Monday';
case 2: return 'Tuesday';
case 3: return 'Wednesday';
case 4: return 'Thursday';
case 5: return 'Friday';
case 6: return 'Saturday';
default: return 'Invalid day';
}
}
console.log(getDayName(3)); // 'Wednesday'
console.log(getDayName(6)); // 'Saturday'
console.log(getDayName(9)); // 'Invalid day'
// โโ switch with break (without return) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function describeWeather(condition) {
let advice;
switch (condition) {
case 'sunny':
advice = 'Wear sunscreen and sunglasses.';
break;
case 'rainy':
advice = 'Bring an umbrella.';
break;
case 'snowy':
advice = 'Dress in warm layers.';
break;
case 'windy':
advice = 'Secure loose items outside.';
break;
default:
advice = 'Check the forecast before heading out.';
}
return advice;
}
console.log(describeWeather('rainy')); // 'Bring an umbrella.'
console.log(describeWeather('cloudy')); // 'Check the forecast...'
// โโ Intentional fall-through โ multiple cases, same block โโโโโโโโโโโโโโโโ
function isWeekend(day) {
switch (day.toLowerCase()) {
case 'saturday':
case 'sunday':
// FALL-THROUGH: both Saturday and Sunday are weekends
return true;
default:
return false;
}
}
console.log(isWeekend('Saturday')); // true
console.log(isWeekend('Monday')); // false
// โโ Object map โ modern alternative to switch โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const HTTP_MESSAGES = {
200: 'OK',
201: 'Created',
400: 'Bad Request',
401: 'Unauthorised',
403: 'Forbidden',
404: 'Not Found',
500: 'Internal Server Error',
};
function getHttpMessage(code) {
return HTTP_MESSAGES[code] ?? `Unknown status code: ${code}`;
}
console.log(getHttpMessage(200)); // 'OK'
console.log(getHttpMessage(404)); // 'Not Found'
console.log(getHttpMessage(418)); // 'Unknown status code: 418'
// โโ switch on string with transformation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function getRoleLabel(role) {
switch (role?.toLowerCase().trim()) {
case 'admin': return 'Administrator';
case 'editor': return 'Content Editor';
case 'viewer': return 'Read-Only Viewer';
case 'guest': return 'Guest User';
default: return 'Unknown Role';
}
}
console.log(getRoleLabel('ADMIN')); // 'Administrator'
console.log(getRoleLabel(' editor ')); // 'Content Editor'
console.log(getRoleLabel(null)); // 'Unknown Role'
How It Works
Step 1 โ Expression Is Evaluated Once
The expression in switch (expression) is evaluated exactly once at the start. JavaScript then walks through each case comparing the result with ===. This is why transforming the expression โ like role?.toLowerCase().trim() โ is done inside the switch parentheses, not repeated in every case.
Step 2 โ break Exits the switch Block
When JavaScript finds a matching case, it executes every statement from that point onwards until it hits a break, a return, or the end of the switch block. Without break, execution “falls through” into the next case’s code โ even if that case’s value does not match.
Step 3 โ Intentional Fall-Through Groups Cases
Placing multiple case labels consecutively with no code between them creates intentional fall-through โ all those cases share the same block. The isWeekend example uses this: both 'saturday' and 'sunday' fall through to the same return true. Always add a comment when doing this intentionally.
Step 4 โ default Is the Fallback
The default case runs when no other case matches โ equivalent to the final else. It does not need to be last (JavaScript will still try all cases first), but placing it last is a strong convention for readability.
Step 5 โ Object Maps Are Often Cleaner
The HTTP_MESSAGES object demonstrates the object-map pattern: store the mapping as data, not logic. Lookup is a single line: HTTP_MESSAGES[code] ?? fallback. This is faster to read, trivially extensible, and can be imported from a separate config file โ none of which is true for a switch block.
Real-World Example: Notification Router
// notification-router.js
// Object map for simple type-to-config lookups
const NOTIFICATION_CONFIG = {
success: { icon: 'check-circle', colour: '#10b981', duration: 3000 },
error: { icon: 'x-circle', colour: '#ef4444', duration: 6000 },
warning: { icon: 'alert-circle', colour: '#f59e0b', duration: 4000 },
info: { icon: 'info', colour: '#3b82f6', duration: 3000 },
};
function createNotification(type, message) {
const config = NOTIFICATION_CONFIG[type] ?? NOTIFICATION_CONFIG.info;
return {
id: `notif-${Date.now()}`,
type,
message,
icon: config.icon,
colour: config.colour,
duration: config.duration,
};
}
// switch for action dispatch โ fall-through used intentionally
function handleKeyboardShortcut(key, ctrlKey) {
switch (true) {
case (ctrlKey && key === 's'):
return 'save';
case (ctrlKey && key === 'z'):
return 'undo';
case (ctrlKey && key === 'y'):
case (ctrlKey && key === 'Z'): // Ctrl+Shift+Z also = redo
// INTENTIONAL FALL-THROUGH: both shortcuts trigger redo
return 'redo';
case (key === 'Escape'):
return 'close';
case (key === 'F1'):
return 'help';
default:
return null;
}
}
console.log(createNotification('success', 'File saved!'));
console.log(createNotification('error', 'Upload failed.'));
console.log(handleKeyboardShortcut('s', true)); // 'save'
console.log(handleKeyboardShortcut('Escape', false)); // 'close'
console.log(handleKeyboardShortcut('a', false)); // null
Common Mistakes
Mistake 1 โ Forgetting break causes fall-through
โ Wrong โ missing break means case ‘b’ code also runs when input is ‘a’:
switch (input) {
case 'a':
console.log('A'); // runs for input 'a'
// no break!
case 'b':
console.log('B'); // ALSO runs for input 'a' โ unintended!
break;
}
โ Correct โ break after every case that should not fall through:
switch (input) {
case 'a':
console.log('A');
break; // stops here
case 'b':
console.log('B');
break;
}
Mistake 2 โ Using switch for range comparisons
โ Wrong โ switch cannot match ranges, only exact values:
switch (score) {
case score >= 90: return 'A'; // always false โ compares number to boolean
}
โ Correct โ use if/else for range-based logic:
if (score >= 90) return 'A';
else if (score >= 80) return 'B';
Mistake 3 โ Type mismatch between expression and cases
โ Wrong โ switch uses ===, string ‘1’ does not match number 1:
const input = '1'; // string from HTML input
switch (input) {
case 1: console.log('one'); break; // never matches โ '1' !== 1
}
โ Correct โ convert type before switching or match string case values:
switch (Number(input)) {
case 1: console.log('one'); break; // now matches
}
Quick Reference
| Pattern | Example | Notes |
|---|---|---|
| Basic switch | switch (x) { case 'a': ... break; } |
Always add break |
| Default case | default: ... |
Runs when nothing matches |
| Intentional fall-through | Two case labels, no code between them | Add comment explaining intent |
| switch(true) | case (x > 10): |
Lets you use expressions in cases |
| Object map | const m = {a:1, b:2}; m[x] ?? def |
Cleaner than switch for simple lookups |