This page looks best with JavaScript enabled

TypeScript Basics: Understanding The "never" Type

 ·  ☕ 5 min read  ·  ✍️ Iskander Samatov

TypeScript Basics Understanding The never Type


The TypeScript never keyword is a bit of a mystery to many developers. What does it do, and when should you use it? Today we will discuss the never keyword in-depth, and cover the situations where you could encounter it.

Characteristics of “never”

TypeScript uses the never keyword to represent situations and control flows that should not logically happen.

In reality, you won’t come across the need to use never in your work very often, but it’s still good to learn how it contributes to type safety in TypeScript.

Let’s see how the documentation describes it: "The never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or assignable to, never (except never itself)."

TLDR; of the above is that you can assign a variable of the type never to any other variable, but you cannot assign other variables to never. Let’s take a look at a code sample:

1
2
3
4
5
const throwErrorFunc = () => { throw new Error("LOL") };
let neverVar: never = throwErrorFunc()
const myString = ""
const myInt:number = neverVar;
neverVar = myString // Type 'string' is not assignable to type 'never'

You can ignore throwErrorFunc for now. Just know that’s a workaround for initializing a variable with type never.

As you can see from the above code, we can assign our variable neverVar of type never to a variable myInt which is of type number. However, we cannot assign the myString variable of type string to neverVar. That will result in a TypeScript error.

You can’t assign variables of any other type to never, even variables of type any.

“never” in functions

TypeScript uses never as a return type for functions that will not reach their return endpoint.

This could mainly happen for two reasons:

  • The function throws an error exception.
  • The function contains an infinite loop.

You've already seen `throwErrorFunc` in our previous code. This is an example of a function that throws an exception:
1
2
3
const throwErrorFunc = () => {
  throw new Error("LOL")
}; // typescript infers the return type of this function to be 'never'

Another case is if you have an infinite loop with a truthy expression that doesn’t have any breaks or return statements:

1
2
3
4
5
const output = () => {
  while (true) {
    console.log("This will get annoying quickly");
  }
} // typescript infers the return type of this function to be 'never'

In both cases, TypeScript will infer that the return type of these functions is never.

Difference between “never” and “void”

So what about the void type? Why do we even need never if we have void?

The main difference between never and void is that the void type can have undefined or null as a value.

TypeScript uses void for functions that do not return anything. When you don’t specify a return type for your function, and it doesn’t return anything in any of the code paths, TypeScript will infer that its return type is void.

In TypeScript, void functions that don’t return anything are actually returning undefined.

1
2
const functionWithVoidReturnType = () => {}; // typescript infers the return type of this function to be 'void'
console.log(functionWithVoidReturnType()); // undefined

However, we usually ignore the return values of the void functions.

Another thing to note here: as per the characteristics of the never type we covered earlier, you cannot assign void to never:

1
2
const myVoidFunction = () => {}
neverVar = myVoidFunction() // ERROR: Type 'never' is not assignable to type 'void'

“never” as the variable guard

Variables can become of the type never if they are narrowed by a type guard that can never be true. Usually, this indicates a flaw in your conditional logic.

Let’s take a look at an example:

1
2
3
4
5
6
7
  const unExpectedResult = (myParam: "this" | "that") => {
    if (myParam === "this") {
    } else if (myParam === "that") {
    } else {
      console.log({ myParam })
    }
  }

In the above example, when the function execution reaches the line console.log({ myParam }), the type of myParam will be never.

This happens because we’re setting the type of myParam to be either “this” or “that”. Since TypeScript assumes that those two are the only possible choices in this situation, logically, the third else statement should never occur. So TypeScript sets the parameter type to never.

Exhaustive Checks

One of the places you might see never in practice is in exhaustive checks. Exhaustive checks are useful for ensuring that you’ve handled every edge case in your code.

Let’s take a look at how we can add a better type safety to a switch statement using an exhaustive check:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  type Animal = "cat" | "dog" | "bird"

  const shouldNotHappen = (animal: never) => {
    throw new Error("Didn't expect to get here")
  }

  const myPicker = (pet: Animal) => {
    switch(pet) {
      case "cat": {
        // handle cat
        return
      }
      case "dog": {
      // handle dog
        return
      }
    }
    return shouldNotHappen(pet)
  }

When you add return shouldNotHappen(pet), you should immediately see an error:

TypeScript exchaustive check with never error sample

The error message informs you of the case you forgot to include in your switch statement. It's a clever pattern for getting compile-time safety and ensuring you handled all the cases in your switch statement.

Conclusion

As you can see, the never type is useful for specific things. Most of the time, it’s an indication that there are flaws in your code.
But in some situations, such as exhaustive checks, it can be a great tool to help you write safer TypeScript code.

If you’d like to get more web development, React and TypeScript tips consider following me on Twitter, where I share things as I learn them.
Happy coding!

Share on

Software Development Tutorials
WRITTEN BY
Iskander Samatov
The best up-to-date tutorials on React, JavaScript and web development.