TypeScript’s never type, what’s the point ?

At first the never type may seem a slightly odd, possibly even pointless type. It can be used to denote that a function never returns, such as an infinite loop or a function simply throws an Error, hence again, never returns. These use cases don’t seem that useful, but you get the idea. The never type denotes that something should never occur.

Where never becomes a lot more useful is in situations where something should never occur in your application’s control flow, specifically regarding choice operators such as switch statements and if..else when used for exhaustive checks.

Let’s look at an example of this…

You have an type Choice which is currently made up of a union of three values, which looks like this

type Choice = 'A' | 'B' | 'C';

Let’s now create a simple function which returns a string based upon the choice value

function makeChoice(choice: Choice) {
  switch(choice) {
    case 'A': return 'A selected'
    case 'B': return 'B selected'
    case 'C': return 'C selected'
  }
  // should never reach
}

makeChoice('C');

We’re handling all the possible values of the union and all is fine with the world.

TypeScript correctly transpiles the code and if we mistakenly replace the change makeChoice(‘C’) to makeChoice(‘D’) (not included in the union) TypeScript reports the error Argument of type ‘”D”‘ is not assignable to parameter of type ‘Choice’.

What happens is – if we had a default case or if the options within the switch were exhausted and the instruction pointer made it to the comment part of the function – which in this case should never occur (as all values in the union are handled). This is where the never type comes in once all options are exhausted the choice variable in essence becomes a type never. I know, it seems strange, but read on…

So, what if we add a new value to the Choice union, let’s say ‘D’, then we have a bit of a problem, TypeScript will transpile without error but our case statement is not handling the new value and hence this may get into production without warning.

Let’s make this a little more obvious by changing our code to this

function makeChoice(choice: Choice) {
  switch(choice) {
    case 'A': return 'A selected'
    case 'B': return 'B selected'
    case 'C': return 'C selected'
  }
  let c: never = choice;
}

Note: Assuming unused variables are not set as errors in your preferred linter then everything will compile and appear without error.

What’s happening here is that the transpiler knows that all choice options are exhausted, hence choice (if you like) changes to become of type never. Hence the line of code let c: never = choice; is valid as this line/instruction should never occur.

What happens it we add a new choice, so we now add ‘D’ to the Choice union and TypeScript will now report an error. This is because the choice variable may now have another value which is not handled by the switch statement and we’ll get the following error “Type ‘”D”‘ is not assignable to type ‘never'”. Which is good because now when we transpile time we’ll be alerted to a potential issue.

Prior to adding the let c: never = choice if we transpile the code to JavaScript we get the following code, which if we change ‘C’ to ‘D’ will not report any problem (which could be fine or could be bad depending upon our usage).

function makeChoice(choice) {
   switch (choice) {
      case 'A': return 'A selected';
      case 'B': return 'B selected';
      case 'C': return 'C selected';
   }
}
makeChoice('C');

So what we really want to do in our TypeScript file is throw an Error in place of the let c: never = choice, then this will catch non-exhaustive switch statements and will be transpiled into JavaScript to give us a runtime guard to highlight a potential issue.

Let’s create a simple function to handle this so our code now looks like this

function unreachable(param: never): never { 
   throw new Error('should not reach here')
}

function makeChoice(choice: Choice) {
  switch(choice) {
    case 'A': return 'A selected'
    case 'B': return 'B selected'
    case 'C': return 'C selected'
  }
  unreachable(choice);
}

which transpiles to almost exactly the same code.

As the function unreachable never returns, we mark it as such by using the never return type.

If we now try to pass ‘D’ to the function makeChoice in the JavaScript code, an exception will occur so we’re covered at runtime now as well as at tranpsile time.

Another example of using never is in the type NonNullable which is part of TypeScript.

type NonNullable<T> = T extends null | undefined ? never : T;

In this instance if the generic type T is null or undefined then never is returned, hence the following type will actually be of type never and this not assignable to

type A = NonNullable<null>;