DEV Community

Dan Diprose
Dan Diprose

Posted on

Exhaustive switch statements with typescript

A great benefit of typescript is that you may apply type safety to your code. While not being required to, adding type safety to the code you write, will help massively when it comes to changing it later.

One of the lesser known aspects of typescript, that due to the dynamic and flexible nature of javascript, the type system of typescript was designed in a way that allowed them to add features that aren't often in other languages.

A type of special note is the never type. As described in the typescript handbook:

The never type represents the type of values that never occur. For instance, never is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns; Variables also acquire the type never when narrowed by any type guards that can never be true.

Sometimes the code you are working with contains switch statements that contain several cases. At times, new cases may need to be added, and it is easy to forget to add these.

While having to update switch statements can often indicate code that isn't so great, typescript contains a language construct to be able to write an exhaustive switch statement, though the use of the never type.

Now lets look at how we can apply this knowledge. Lets say we had this code that is supposed to handle all cases of the enum MyEnum.

enum MyEnum {
  Case1 = "Case1",
  Case2 = "Case2",
  Case3 = "Case3"
}

const input: MyEnum = <any>MyEnum.Case1;

switch(input) {
  case MyEnum.Case1:
    console.log('process case 1!');
    break;
  case MyEnum.Case2:
    console.log('process case 2!');
    break;
}

Ooops! Looks like we forgot to handle Case3. We can certainly fix this by adding the missed case to this switch statement, however wouldn't it be good if we had some way to know at compile time that we had missed a case? It turns out we can with typescript using the following:

enum MyEnum {
  Case1 = "Case1",
  Case2 = "Case2",
  Case3 = "Case3"
}

const input: MyEnum = <any>MyEnum.Case1;

function assertUnreachable(x: never): never {
  throw new Error("Didn't expect to get here");
}

switch(input) {
  case MyEnum.Case1:
    console.log('process case 1!');
    break;
  case MyEnum.Case2:
    console.log('process case 2!');
    break;
  default:
    assertUnreachable(input);
}

Now, this will fail to compile because we didn't handle all of the MyEnum values:

index.ts:21:23 - error TS2345: Argument of type 'MyEnum.Case3' is not assignable to parameter of type 'never'.

This is because input can actually be MyEnum.Case3, so can not be assigned to the never type. As such, we have successfully used typescript to detect that we have missed a case.

To fix the code now, all we have to do is add the following to the switch statement to handle MyEnum.Case3, and it will compile.

  case MyEnum.Case3:
    console.log('process case 3!');
    break;

Note: all is well and good here, however what happens if input actually comes from an outside source (e.g. a json payload from an api), and is just assumed to be of the enum type?

In this case we can see the issue that might occur. To illustrate this, let us define input as:

const input: MyEnum = <any>"Foo"; // some value from an external source

Then although it would compile, we would get the error thrown when running it:

Error: Didn't expect to get here

In order to handle this, we must be careful about values that come in from an external source. One way is to add validation to external input. For example JSON Schema could be used to validate the external input before it reaches this part of the code.

In any case, we must be careful to handle these scenarios, and to realise that if not handled properly, the runtime error above could occur. So, be sure to validate your payload, or at least handle that possible run time error.

That's all for now. In this article, the take away here is that when using switch statements, typescript gives you a way to exhaustively check that all cases are handled in a switch statement that uses enums. A handy feature that isn't available in most other languages, giving yet another solid reason to be using typescript as your type-safe language of choice for both the front and back end.

Top comments (0)