Making decisions · Lesson 12 of 77 · concept
switch, and when to avoid it
About 45 minutes.
By the end of this lesson you can
- Write a switch statement with case labels, break and default.
- Explain that switch matches with strict equality, not loose comparison.
- Use deliberate fall-through to group cases, and recognise the accidental kind as a bug.
- Decide when an if / else chain reads better than a switch.
One value, many labelled sockets
An analogy
Some decisions are not a ladder of different questions. They are the same question asked once, with a list of possible answers: which month is this, which status code came back, which button was pressed. Writing that as 'else if (month === "january") ... else if (month === "february")' works, but you end up repeating the variable name and the === on every single line, and the one thing that actually varies gets lost in the noise.
A switch is the shape for that case. Picture an old telephone switchboard: one incoming call, a panel of labelled sockets, and the operator plugs it into the socket whose label matches. You state the value once at the top, then list the labels.
The shape of a switch
Explanation
You write 'switch (value) {' and then, inside the braces, a series of 'case someLabel:' lines. When the switch runs, JavaScript compares the value against each case label in turn and jumps to the first one that matches. The statements after that label run - and they are not wrapped in braces of their own, which is unusual and is the source of the surprise in the next section.
'break;' ends the switch and continues after its closing brace. 'default:' is the label for 'nothing matched', and it plays the same role as the final else in a chain: without it, an unmatched value simply does nothing at all, silently. Put default last, where a reader expects to find it, and write it even when you believe every case is covered.
Inside a function you will often see 'return' used instead of 'break'. That works because returning leaves the function entirely, so it ends the switch as a side effect. Whichever you use, be consistent within one switch.
The comparison is ===, and it will not meet you halfway
Explanation
Matching a case is done with strict equality - the same === you already know. There is no type conversion. A switch on the text "1" will not match 'case 1:', and a switch on the text "February" will not match 'case "february":' because the capital letter makes it a different value.
This is a good thing: it means a switch never surprises you with a hidden conversion. It also means that when a switch mysteriously falls to its default, the first thing to check is the type and the exact spelling of the value coming in - values arriving from a form or a file are text far more often than beginners expect.
Check there: The switch expression is compared with each case using strict equality, and execution continues into subsequent cases until a break is encountered.
Fall-through: a feature and a trap wearing the same clothes
A common mistake
Once a switch has jumped to a matching label it keeps running forward through the following statements, and it does NOT stop at the next case label. It carries on until it meets a break, a return, or the closing brace of the switch. That behaviour is called fall-through, and it is why break exists at all.
Forgetting a break is therefore not a syntax error and not a warning. It is a program that quietly does two things where you meant one, and it usually shows up as a bug report about one specific input rather than as a crash.
The same behaviour is genuinely useful when you stack several labels with nothing between them - 'case "saturday": case "sunday": return "weekend";' - because a match on either label lands you in the same block. That is the deliberate kind. The way to keep them apart when reading code is simple: labels stacked with no statements between them are intentional; a label reached after real statements that lack a break is a bug until proven otherwise.
One sharp edge: the whole switch body is a single block
A common mistake
Case labels look like they create separate compartments. They do not. The braces of the switch form one single block, and every case shares it, so declaring 'let total' in one case and 'let total' in another is declaring the same name twice in the same scope - a SyntaxError, and the program refuses to start.
The fix is to give the case its own braces: 'case "a": { let total = 1; break; }'. Those braces are a block statement of exactly the kind you met in the first lesson, and they give the declaration somewhere private to live. Many teams write every non-trivial case that way as a matter of habit.
When a switch is the wrong tool
Why it matters
A switch can only ask one kind of question: is this value exactly equal to that one? The moment a branch needs a range ('under 5'), a comparison ('longer than the last one'), or two conditions combined with &&, a switch cannot express it. You will see people force it by writing 'switch (true)' and putting real conditions in the case labels; it works, and it is a disguised if / else chain that is harder to read than the real thing. Use the chain.
The other limit is length. Twelve cases that each return a fixed value are really a table of data pretending to be code - and a table belongs in a data structure you can look a value up in, which is what the data-modelling module builds. That is not available to you yet, so a switch is the right answer today; it is worth knowing now that it is not the last word.
The honest summary: reach for a switch when you have one value, several exact matches, and a sensible default. Reach for if / else when the branches ask different questions.
Recap
Recap
A switch tests one value against exact case labels using ===, jumps to the first match, and keeps running until a break or return. Stacked labels with nothing between them share a body deliberately; a missing break is the same behaviour by accident. default catches everything else and belongs last. All the cases share one block, so a let declaration in a case needs its own braces. When a branch needs a range or a combined condition, use if / else instead.
Worked examples
Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.
A switch with break and default
The plain shape: one value at the top, exact labels, a break to close each branch, and a default for everything unlisted.
1const code = "slow";2let message;3 4switch (code) {5 case "ok":6 message = "Everything worked.";7 break;8 case "slow":9 message = "It worked, but it was slow.";10 break;11 default:12 message = "Unknown status.";13}14 15console.log(message);- line 4
The value is named once, here, instead of being repeated in every branch. That is most of what a switch buys you.
- line 8
The first label that is strictly equal to the value. Execution jumps to the statement below it.
- line 10
Without this break, execution would continue straight into the default branch below and overwrite the message.
- line 11
The catch-all. It needs no break here because it is last, but many teams write one anyway so that adding a case later cannot break it.
What it prints
It worked, but it was slow.The missing break, caught in the act
One case is missing its break. Read the output carefully: the first call produces two lines when it should produce one, and nothing anywhere reports an error.
1function order(size) {2 switch (size) {3 case "large":4 console.log("large cup");5 case "medium":6 console.log("medium cup");7 break;8 case "small":9 console.log("small cup");10 break;11 }12}13 14order("large");15order("small");- line 4
This branch has no break, so after printing 'large cup' execution simply carries on downward.
- line 5
A case label is only a jump target, not a wall. Execution that is already running walks straight past it and prints the medium line too.
- line 8
This case is unaffected: the break above it stops execution before it can be reached by accident.
What it prints
large cupmedium cupsmall cup
Fall-through used deliberately
Stacked labels with no statements between them are the intentional use of the same behaviour: several values, one answer, no repetition.
1function dayType(day) {2 switch (day) {3 case "saturday":4 case "sunday":5 return "weekend";6 default:7 return "weekday";8 }9}10 11console.log(dayType("sunday"));12console.log(dayType("monday"));- line 3
No statements between the two labels, so a match on 'saturday' falls through to the shared body below. This is the readable way to say 'either of these'.
- line 5
return ends the whole function, so no break is needed. Mixing return and break in one switch is legal but reads badly - pick one.
What it prints
weekendweekday
Why the text "1" does not match case 1
Proof that the comparison is strict. The same-looking value of a different type falls through to its own case, and a boolean matches nothing at all.
1function pick(value) {2 switch (value) {3 case 1:4 return "the number one";5 case "1":6 return "the text one";7 default:8 return "something else";9 }10}11 12console.log(pick(1));13console.log(pick("1"));14console.log(pick(true));- line 5
A separate case, because "1" and 1 are different values under ===. If switch used loose comparison this label would be unreachable.
- line 13
true is not strictly equal to 1, so nothing matches and default answers. Under the loose == operator this would have matched the first case - another reason strict matching is the safer rule.
What it prints
the number onethe text onesomething else
Check yourself
Not scored, not stored. Getting one wrong is the useful part.
Only one case here has a break. Work out how many lines this prints before you look - the answer is not one.
switch (2) { case 1: console.log("one"); case 2: console.log("two"); case 3: console.log("three"); break; case 4: console.log("four");}
A form field always hands you text. You write switch (quantity) with 'case 2:' and 'default:'. The user types 2. Which branch runs?
Which of these rules can a switch NOT express directly?
Write it yourself
Exercise · core · 9 tests
How many days in that month?
Write daysInMonth(month, isLeapYear) where month is the lowercase English month name. Return 31 for january, march, may, july, august, october and december; 30 for april, june, september and november; 29 for february when isLeapYear is true and 28 when it is not; and null for anything that is not one of the twelve names. Group the cases that share an answer by stacking their labels rather than repeating the body, and remember the matching is exact - 'February' with a capital F is not one of the twelve names.
daysInMonth returns the number 31, 30, 29 or 28 for a valid lowercase month name, choosing between 29 and 28 for february according to isLeapYear, and returns null for any value that is not one of the twelve lowercase names.
Define daysInMonth at the top level of your code — the tests call it by name.
Worth remembering
- How does a switch decide whether a case matches?
Strict equality (===), with no type conversion. The text "1" never matches case 1, and "February" never matches case "february".
- What happens after a matching case body finishes, if there is no break?
Execution continues into the following cases regardless of their labels, until it meets a break, a return, or the end of the switch.
- Why can declaring 'let total' in two different cases be a SyntaxError?
The whole switch body is one block, so both declarations are in the same scope. Give the case its own braces to fix it.
Check this lesson against the source
We wrote the explanation; we did not invent the facts. This is the page that backs them.
Check there: Cases are matched with strict equality; execution falls through subsequent cases until break; default handles no-match; and the switch body is a single block scope.
Check there: === returns false when the operands are of different types, with no conversion.
Check there: break terminates the enclosing switch statement and transfers control after it.
Prefer to read the source in a structured breakdown? Turn this doc into a breakdown