The "switch" statement
When you find yourself writing the same variable name over and over in a chain of if checks, switch is usually the cleaner tool. It takes one value, lines up several candidate values next to it, and jumps to whichever one matches.
Think of it as a labeled directory. You walk in holding a value, scan the labels top to bottom, and the moment one matches you stop reading labels and start doing work.
The syntax
A switch has a head — the value you’re testing — followed by one or more case blocks and an optional default.
switch(x) {
case 'value1': // if (x === 'value1')
...
[break]
case 'value2': // if (x === 'value2')
...
[break]
default:
...
[break]
}
The brackets around break mean it’s optional syntactically, not that you should skip it. Here’s what actually happens when the engine runs a switch:
- It compares
xagainst the firstcasevalue (value1) using strict equality (===), then the second (value2), and so on down the list. - The first match wins. Execution jumps to that
caseand runs everything below it until it hits abreakor falls off the end of theswitch. - If nothing matches, the
defaultblock runs — assuming you wrote one.
An example
Here the matched block is highlighted:
let guess = 3 * 2;
switch (guess) {
case 5:
alert( 'Undershot' );
break;
case 6:
alert( 'Bullseye!' );
break;
case 7:
alert( 'Overshot' );
break;
default:
alert( 'Way off' );
}
guess is 6. The switch starts comparing at the top: is 6 === 5? No. Is 6 === 6? Yes — so execution enters case 6, shows 'Bullseye!', and the break ends the whole statement. The remaining cases never get looked at.
Change the number below and run it. Each value jumps to exactly one block (or default when nothing matches):
That break is doing real work. Drop it and the behavior changes completely.
Watch what happens when we remove every break:
let guess = 3 * 2;
switch (guess) {
case 5:
alert( 'Undershot' );
case 6:
alert( 'Bullseye!' );
case 7:
alert( 'Overshot' );
default:
alert( 'Way off' );
}
guess is still 6, so execution enters at case 6 as before. But now there’s no wall to stop it. It runs 'Bullseye!', spills into case 7 and runs 'Overshot', then spills into default and runs the last one. Three alerts fire in a row:
alert( 'Bullseye!' );
alert( 'Overshot' );
alert( 'Way off' );
Toggle the break statements on and off below to see the difference for yourself. guess is fixed at 6, so execution always enters at case 6 — but whether it stops there or spills onward is entirely up to break:
Fall-through is a frequent source of bugs. If you forget a single break, the code below it runs by accident and the mistake can be hard to spot. Some linters flag a case that falls through unless you mark it as intentional. Keep the habit: one break per case unless you’re grouping on purpose (more on that below).
Grouping of “case”
Because fall-through runs every block below the entry point, you can stack case labels to make several values share one block of code.
Suppose you want the same message for case 5 and case 7:
let guess = 5;
switch (guess) {
case 6:
alert('Nailed it!');
break;
case 5: // (*) grouped two cases
case 7:
alert('So close!');
alert('Try nudging your aim.');
break;
default:
alert('That is nowhere near. Really.');
}
Now 5 and 7 produce identical output. When guess is 5, execution enters at line (*). There’s no code and no break between case 5 and case 7, so it falls straight through into the shared body. When guess is 7, it enters one line lower and runs the same body.
This isn’t a special “OR” feature — it’s the same fall-through you saw earlier, used deliberately. An empty case with no break just means “and also handle this value the same way.”
alert(‘Try nudging your aim.’);
break;
Here’s grouping put to practical use: cases 1–5 stack to share one “weekday” block, and 6–7 share a “weekend” block. Enter a day number and watch several values land on the same result:
Type matters
The comparison a switch performs is always strict — the same === you use elsewhere. Value and type both have to line up, with no automatic conversion.
That last part trips people up constantly. Look at this:
let arg = prompt("Pick a lane (1-3)?");
switch (arg) {
case '1':
case '2':
alert( 'Left side' );
break;
case '3':
alert( 'Middle' );
break;
case 4:
alert( 'Never executes!' );
break;
default:
alert( 'An unknown value' );
}
Trace it by input:
- Type
1or2, and the grouped first block runs'Left side'. - Type
3, and'Middle'runs. - Type
4, and… thedefaultruns. Notcase 4.
Why? prompt always hands back a string, so arg holds the text "4". The case 4 label is the number 4. Since "4" === 4 is false under strict equality, that case can never match — it’s dead code.
The fix is to match the type you actually have. If the case labels are strings ('3', '4'), use case '4':. If you need numeric logic, convert the input first with switch (Number(arg)) so you’re comparing numbers to numbers. Either way, keep both sides of every comparison in the same type and this class of bug disappears.
See the dead case for yourself. The input is always text (just like prompt), so typing 4 lands on default — the number label case 4 can never match. Then flip the toggle to convert with Number(...) and watch 4 suddenly reach the numeric case: