The Case against Switch

I’ve never been fond of switch statements, whether in JavaScript or Java. They’re big and hard to follow, and of course if you forget the break keyword after each case you enter fall-through hell. (Since break statements are almost always intended it seems a pain to have to add them manually). Using objects as hash table for look-up is a simple and elegant alternative:

Example 1: Using switch is hard to read and the data is mixed with the logic

var whatToBring;
switch(weather) {
    case "Sunny":
        whatToBring = "Sunscreen and hat";
        break;
    case "Rain":
        whatToBring  ="Umbrella and boots"
        break;
    case "Cold":
        whatToBring = "Scarf and Gloves";
        break;
    default : whatToBring = "Play it by ear";
}

Example 2: Pull data into object construct. Data and logic are separated.

var whatToBring = {
    "Sunny" : "Sunscreen and hat",
    "Rain" : "Umbrella and boots",
    "Cold" : "Scarf and Gloves",
    "Default" : "Play it by ear"
}

var gear = whatToBring[weather] || whatToBring["Default"];

22 thoughts on “The Case against Switch

  1. I have used the construct on many occasions at work. Its a beautiful way to extract data and avoid unnecessary conditional statements that clutter up code… Nice post sir..

  2. Wish I had known that about a year ago working on a large JS application. Would have simplified some of my logic quite a bit. Good technique, I applaud you. 🙂

    1. but…it would’ve slowed it down. Programmers have to take performance into consideration when hacking and from time to time we have to think of lower layers. Abstraction added on the “new” programming languages, to make our lives easier also it’s quite generic, and as such care needs to be taken when coding. Yes, it looks nicer, yes it’s easier to change, but, is it faster? How does this impact long term development, etc.

  3. Looks like you have switched “gear” and “whatToBring” in the last example.

    Shouldn’t it read as follows?

    var gear = whatToBring[weather] ? whatToBring[weather] : whatToBring[“Default”];

  4. If Object.prototype changes, or is different on different interpreters, then this can break.

    Imagine if the next version of EcmaScript defines a new property Object.prototype.Cold, the switch statement would keep working but whatToBring[weather] would break.
    And if Object.prototype.Cold is a proprietary extension on only one browser, now you have just added hard-to-debug cross-browser issues to your troubles.

    It would be a little better if the idiom read
    whatToBring.hasOwnProperty(weather) ? whatToBring[weather] : whatToBring.Default
    but that loses the elegance of the original.

    1. Mike, thanks- yes you’re right in theory. But the issue you raise goes way deeper than my example. The strategy of checking for the existence of a property and otherwise following a default action is considered safe enough that even the major JavaScript framework providers do it.

      For example if ECMA 6 implemented any of ‘statusText’, ‘success’, ‘failure’ or ‘frequency’ on Object.prototype (which is much more likely than them implementing “Cold”) then Prototype.js fails. They’re confident that the calculated risk pays off, and so am I.

      (These lines are all in prototype.js 1.6.1)

      return this.transport.statusText || '';
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
      this.frequency = (this.options.frequency || 2);

      In fact don’t we always run this risk whenever any object is being checked for any property – period? Are you saying that any code that tests for a.b under any scenario is always unsafe, since Object’s prototype could be belatedly augmented with property b?

      But, sure, if in doubt use hasOwnProperty(). Or much better, initialize your properties to null.

  5. You can go one more step ahead.
    If you have function to do the work for you in the switch statements
    then

    var switcher = { };
    switcher[“Good”] = function() { alert(” Do some good work”); };
    switcher[“Bad”] = function() { alert(” Do some bad work”); };
    switcher[“Ugly”] = function() { alert(” Do some ugly work”); };

    switcher[someVar]();

  6. var gear = (  (weather = "Sunny") ? "Sunscreen and hat"
                : (weather = "Rain")  ? "Umbrella and boots"
                : (weather = "Cold")  ? "Scarf and Gloves"
                : "Play it by ear");
    
    1. @fd: Dude are you sure your js code works? I think you mean (weather == “sunny”) e.t.c but i am not sure. Also using a nested ternary makes it much harder to read. (my 2 cents)

    2. @Code Pimp: you’re correct, they should be ==.
      @fd: this is undesirable compared to the solution proposed by Angus; in a larger application, Angus’s approach allows you to maintain one set of strings and update one central location rather than every switch/nested ternary where that string might exist. Plus, as Code Pimp points out, the nested ternary operators are a little more confusing to the brain.

  7. I just replaced a switch today that had a collection of case statements (with an if). All falling through to the last case.
    I’ll be replacing the if tomorrow with this construct.
    This is a lovely replacement for the switch.
    Cheers Angus.

  8. I dont disagree with your example. However I strongly disagree with your pre-disposition and question your understanding of what case statements are meant for.

  9. I know I’m late to the party and I understand what you mean (I also abuse objects in the same way), but that’s just scratching the surface. Switch statements are more complex than that. Here’s a simplified way I enjoy using them:

    var whatToBring = [],
        goingToTheBeach = true,
        isRaining = false,
        camping = false;
        people = 6;
    
    switch (true) {
        case goingToTheBeach:
            // break left out intentionally; you'll need an umbrella too :)
            whatToBring.push('Sunscreen');
        case isRaining:
            whatToBring.push('Umbrella');
        break;
    
        case camping && people > 4:
            whatToBring.push('Large tent');
        break;
    
        case camping:
            whatToBring.push('Small tent');
        break;
    
        default:
            whatToBring.push('Nothing fancy');        
        break;
    }
    
    // whatToBring = ['Sunscreen', 'Umbrella'];
    

Leave a reply to Biswanath Cancel reply