The Curious Case of Switch Statements in JavaScript
When you start learning an imperative programming language like JavaScript, you quickly get to control flow structures like conditions and loops. Soon after that, you'll probably see your first switch
statement, and you might think it's a nice replacement for cascading if
-else
constructs in some cases.
Indeed, switch
statements can be quite straightforward:
function signToString(num) {
switch (Math.sign(num)) {
case 1:
return "positive";
case 0:
return "zero";
case -1:
return "negative";
default:
return "NaN";
}
}
In the above code snippet, I would argue that it's quite easy to follow the logic. We quickly see that the switch
statement is exhaustive because of its default
clause. Finally, there's no implicit fallthrough since every case's statement list contains an unconditional return
statement.
Alright, switch
statements like that seem to be easy enough, but what if they get more complicated? Then they suddenly become a lot less straightforward.
#More Complex switch
Statements
Assume you stumble upon the following switch
statement:
switch (value) {
case 1:
console.log("1");
case 2:
console.log("2");
break;
default:
console.log("default");
case 3:
console.log("3");
case 4:
console.log("4");
break;
case 5:
console.log("5");
case 6:
}
I'd like you to take a minute and think about what console output you'll see if you plug in all values from 1 through 7 into the local variable value
. You can simply execute the switch
statement to see if you're right, but make sure to ponder the output first.
switch
statements like the above are very misleading, and you can get confused quickly if you don't know their precise semantics and order of evaluation. Take a look at the section on switch
statements of the language specification and you'll probably agree with me. It's not rocket science, but it's not trivial, either.
What might be confusing in the above code snippet is the position of the default
case. It doesn't matter where it's defined; its statement list is only executed when none of the other cases match or when a case above it falls through.
#Solution: Console Output
Now, let's take a detailed look at the console output:
- If
value
is1
, the output will be"1"
and"2"
. The first case doesn't end with abreak
statement and thus falls through to the second case. - If
value
is2
, the output will be just"2"
. A single case with a single log statement followed bybreak
. - If
value
is3
, the output will be"3"
and"4"
. The semicolon represents an empty statement which doesn't do anything; we also have a fallthrough to the fourth case. Becausecase 3
matchesvalue
, thedefault
case is not executed. - If
value
is4
, the output will be just"4"
. Similar to previous case, thedefault
case is not executed. - If
value
is set to5
, the output will be just"5"
. Since there's nobreak
, we have a fallthrough tocase 6
, which has an empty statement list. We thus fall out of theswitch
statement and resume normal control flow afterwards. - If
value
is6
, there won't be any console output at all because this case has an empty statement list. - If
value
is42
or any other value different from the ones before, the output will be"default"
,"3"
, and"4"
. All of the case clauses are compared againstvalue
in the order they're defined. Because none of them matches42
, thedefault
case is executed, and since it doesn't end with abreak
statement, it falls through to the next case like any other case would.
#Summary
There's nothing wrong with using switch
statements in general, but when you do, make sure they're trivial to understand and unambiguous to the reader.
It might help to make intended fallthroughs explicit by adding short comments to the respective clause, for instance: // Fallthrough!