You might not need switch in JavaScript

Pardon the clickbaity title, but JavaScript developers are so fond of switch. Is there a better alternative?

tl;dr: there is, but it's slower.

No switch, no party

Java folks are so fond of switch, and so are JavaScript developers. Let's be honest, we developers are lazy, and for people like me who lacks creativity it's easy to stick with the status quo.

switch is convenient: given an expression we can check if it matches with something else in a bunch of case clauses. Consider this example:

const name = "Juliana";

switch (name) {
  case "Juliana":
    console.log("She's Juliana");
    break;
  case "Tom":
    console.log("She's not Juliana");
    break;
}

When the name is "Juliana" we print a message, and we immediately exit from the block with break. When switch is inside a function you may omit break, as long as you return from each clause.

You can also have a default branch for when nothing matches:

const name = "Kris";

switch (name) {
  case "Juliana":
    console.log("She's Juliana");
    break;
  case "Tom":
    console.log("She's not Juliana");
    break;
  default:
    console.log("Sorry, no match");
}

switch is also heavily used in Redux reducers (although Redux Toolkit simplified the boilerplate) to avoid a tonne of if. Consider this example:

const LOGIN_SUCCESS = "LOGIN_SUCCESS";
const LOGIN_FAILED = "LOGIN_FAILED";

const authState = {
  token: "",
  error: "",
};

function authReducer(state = authState, action) {
  switch (action.type) {
    case LOGIN_SUCCESS:
      return { ...state, token: action.payload };
    case LOGIN_FAILED:
      return { ...state, error: action.payload };
    default:
      return state;
  }
}

What's wrong with it? Almost nothing. But is there a better alternative?

Learning from Python

This tweet from Telmo caught my eyes. He shows two styles for "switching", one of witch is so close to a similar pattern in Python.

Python has no switch, and it can teach us a better alternative to our beloved, cluttered switch. Let's first port our code from JavaScript to Python:

LOGIN_SUCCESS = "LOGIN_SUCCESS"
LOGIN_FAILED = "LOGIN_FAILED"

auth_state = {"token": "", "error": ""}


def auth_reducer(state=auth_state, action={}):
    mapping = {
        LOGIN_SUCCESS: {**state, "token": action["payload"]},
        LOGIN_FAILED: {**state, "error": action["payload"]},
    }

    return mapping.get(action["type"], state)

In Python, we can simulate switch with a dictionary. The default case is basically replaced by the default for dict.get().

Python raises a KeyError when you access an inexistent key:

>>> my_dict = {
    "name": "John", 
    "city": "Rome", 
    "age": 44
    }

>>> my_dict["not_here"]

# Output: KeyError: 'not_here'

The .get() method is a safer alternative to direct key access, because it does not raise, and lets specify a default value for inexistent keys:

>>> my_dict = {
    "name": "John", 
    "city": "Rome", 
    "age": 44
    }

>>> my_dict.get("not_here", "not found")

# Output: 'not found'

So this line in Python:

# omit
    return mapping.get(action["type"], state)

is equivalent in JavaScript to:

function authReducer(state = authState, action) {
// omit
    default:
      return state;
  }
}

What's my point then? Let's apply the same Pythonic style to JavaScript.

An alternative to switch

Consider again the previous example:

const LOGIN_SUCCESS = "LOGIN_SUCCESS";
const LOGIN_FAILED = "LOGIN_FAILED";

const authState = {
  token: "",
  error: "",
};

function authReducer(state = authState, action) {
  switch (action.type) {
    case LOGIN_SUCCESS:
      return { ...state, token: action.payload };
    case LOGIN_FAILED:
      return { ...state, error: action.payload };
    default:
      return state;
  }
}

If we want to get rid of that switch we can do something along these lines:

function authReducer(state = authState, action) {
  const mapping = {
    [LOGIN_SUCCESS]: { ...state, token: action.payload },
    [LOGIN_FAILED]: { ...state, error: action.payload }
  };

  return mapping[action.type] || state;
}

Way much cleaner if you ask me. This nice refactoring is possible thanks to modern JavaScript, namely computed property names (ECMAScript 2015):

  const mapping = {
    [LOGIN_SUCCESS]: { ...state, token: action.payload },
    [LOGIN_FAILED]: { ...state, error: action.payload }
  };

Here properties for mapping are computed on the fly from two constants: LOGIN_SUCCESS and LOGIN_FAILED. Then there is object spread (ECMAScript 2018):

  const mapping = {
    [LOGIN_SUCCESS]: { ...state, token: action.payload },
    [LOGIN_FAILED]: { ...state, error: action.payload }
  };

Here the values for this object are objects obtained from spreading the original state into a fresh object.

How do you like this approach? Of course, it has some limitations over a proper switch, yet for a reducer could be a convenient technique.

But, how does this code performs?

How about performances?

Switch outperforms fancy syntax.

Performance wise switch is unsurprisingly faster than the mapping version. You can do some sampling with the following snippet, just replace the version of authReducer with the mapping version after testing switch:

console.time("sample");
for (let i = 0; i < 2000000; i++) {
  const nextState = authReducer(authState, {
    type: LOGIN_SUCCESS,
    payload: "some_token"
  });
}
console.timeEnd("sample");

Measure them ten times or so with:

for t in {1..10}; do node switch.js >> switch.txt;done
for t in {1..10}; do node map.js >> map.txt;done

Here's my results for ten consecutive samples (milliseconds):

Switch Mapping
43.033 71.609
40.247 64.800
37.814 66.123
37.967 63.871
37.616 68.133
38.534 69.483
37.261 67.353
41.662 66.113
38.867 65.872
37.602 66.873

Feel free to draw your own conclusions. Thanks for reading!

Resources

JavaScript switch statement

Valentino Gagliardi

Hi! I'm Valentino! I'm a freelance consultant with a wealth of experience in the IT industry. I spent the last years as a frontend consultant, providing advice and help, coaching and training on JavaScript, testing, and software development. Let's get in touch!