Marius Schulz
Marius Schulz
Front End Engineer

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 is 1, the output will be "1" and "2". The first case doesn't end with a break statement and thus falls through to the second case.
  • If value is 2, the output will be just "2". A single case with a single log statement followed by break.
  • If value is 3, 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. Because case 3 matches value, the default case is not executed.
  • If value is 4, the output will be just "4". Similar to previous case, the default case is not executed.
  • If value is set to 5, the output will be just "5". Since there's no break, we have a fallthrough to case 6, which has an empty statement list. We thus fall out of the switch statement and resume normal control flow afterwards.
  • If value is 6, there won't be any console output at all because this case has an empty statement list.
  • If value is 42 or any other value different from the ones before, the output will be "default", "3", and "4". All of the case clauses are compared against value in the order they're defined. Because none of them matches 42, the default case is executed, and since it doesn't end with a break 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!