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
valueis1, the output will be"1"and"2". The first case doesn't end with abreakstatement and thus falls through to the second case. - If
valueis2, the output will be just"2". A single case with a single log statement followed bybreak. - If
valueis3, 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 3matchesvalue, thedefaultcase is not executed. - If
valueis4, the output will be just"4". Similar to previous case, thedefaultcase is not executed. - If
valueis 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 theswitchstatement and resume normal control flow afterwards. - If
valueis6, there won't be any console output at all because this case has an empty statement list. - If
valueis42or any other value different from the ones before, the output will be"default","3", and"4". All of the case clauses are compared againstvaluein the order they're defined. Because none of them matches42, thedefaultcase is executed, and since it doesn't end with abreakstatement, 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!