The never Type in TypeScript
In TypeScript 2.0, a new primitive type called never
was introduced. It represents the type of values that never occur. The never
type is used in the following two places:
- As the return type of functions that never return.
- As the type of variables under type guards that are never true.
These are the exact characteristics of the never
type as described here:
never
is a subtype of and assignable to every type.- No type is a subtype of or assignable to
never
(exceptnever
itself). - In a function expression or arrow function with no return type annotation, if the function has no
return
statements, or onlyreturn
statements with expressions of typenever
, and if the end point of the function is not reachable (as determined by control flow analysis), the inferred return type for the function isnever
. - In a function with an explicit
never
return type annotation, allreturn
statements (if any) must have expressions of typenever
and the end point of the function must not be reachable.
Let's take a look at practical use cases where we'd use the never
type.
#Functions That Never Return
Here's an example of a function that never returns:
// Type () => never
const sing = function() {
while (true) {
console.log("Never gonna give you up");
console.log("Never gonna let you down");
console.log("Never gonna run around and desert you");
console.log("Never gonna make you cry");
console.log("Never gonna say goodbye");
console.log("Never gonna tell a lie and hurt you");
}
};
The body of the function expression consists of an infinite loop that doesn't contain any break
or return
statements. There's no way to break out of the loop, given that console.log
doesn't throw. Therefore, never
is inferred for the function's return type.
Similarly, the return type of the following function is inferred to be never
:
// Type (message: string) => never
const failwith = (message: string) => {
throw new Error(message);
};
TypeScript infers the never
type because the function neither has a return type annotation nor a reachable end point, as determined by control flow analysis.
#Variables with Impossible Types
Another case where the never
type is inferred is within type guards that are never true. In the following example, we check whether the value
parameter is both a string and a number, which is impossible:
function impossibleTypeGuard(value: any) {
if (
typeof value === "string" &&
typeof value === "number"
) {
value; // Type never
}
}
This example was obviously contrived, so let's look at a more realistic use case. The example below showcases that TypeScript's control flow analysis narrows union types of variables under type guards. Intuitively speaking, the type checker knows that value
cannot be a number once we've checked that it's a string, and vice-versa:
function controlFlowAnalysisWithNever(
value: string | number
) {
if (typeof value === "string") {
value; // Type string
} else if (typeof value === "number") {
value; // Type number
} else {
value; // Type never
}
}
Note that value
can neither be a string nor a number within the last else
branch. In that case, TypeScript infers the never
type because we've annotated the value
parameter to be of type string | number
, that is, no other type than string
or number
is possible for the value
parameter.
Once control flow analysis has eliminated both string
and number
as candidates for the type of value
, the type checker infers the never
type, the only possibility remaining. However, we cannot do anything useful with value
because it's of type never
, so our editor tooling doesn't show any autocompletion suggestions:
#The Difference Between never
and void
You might ask yourself why TypeScript needs a never
type when it already has a void
type. Although the two might seem similar, they represent two different concepts:
- A function that doesn't explicitly return a value implicitly returns the value
undefined
in JavaScript. Although we typically say that such a function "doesn't return anything", it returns. We usually ignore the return value in these cases. Such a function is inferred to have avoid
return type in TypeScript. - A function that has a
never
return type never returns. It doesn't returnundefined
, either. The function doesn't have a normal completion, which means it throws an error or never finishes running at all.
If you're interested in type theory, the never
type is a bottom type, also known as a zero type or an empty type. It's often denoted as ⊥ and signals that a computation doesn't return a result to its caller. The void
type, on the other hand, is a unit type (a type that allows only one value) with no defined operations.
#Type Inference for Function Declarations
There's a little gotcha regarding the return type inference of function declarations. If you read closely through the characteristics of the never
type listed at the beginning of this post, you'll find the following sentence:
In a function expression or arrow function with no return type annotation, […]
It mentions function expressions and arrow functions, but not function declarations. That is, the return type inferred for a function expression might differ from the one inferred for a function declaration:
// Return type: void
function failwith1(message: string) {
throw new Error(message);
}
// Return type: never
const failwith2 = function(message: string) {
throw new Error(message);
};
The reason for this behavior is backward compatibility, as explained here. If you want a function declaration to have the return type never
, you can explicitly annotate it:
function failwith1(message: string): never {
throw new Error(message);
}
Great, you've made it this far! As a reward, here's a little video for you. Enjoy!
This article and 44 others are part of the TypeScript Evolution series. Have a look!