Summary: in this tutorial, you will learn about the TypeScript never
type to represent a value that never occurs.
Introduction to the TypeScript never type
In TypeScript, a type is like a set of values. For example, the number
type holds the numbers 1, 2, 3, etc. The string
type holds the strings like 'Hi'
, 'Hello'
, etc. The null
type holds a single value, which is null
.
The never
type is a type that holds no value. It is like an empty set.
Since a never
type does not hold any value, you cannot assign a value to a variable with the never
type.
For example, the following will result in an error:
let empty: never = 'hello';
Code language: JavaScript (javascript)
The TypeScript compiler issues the following error:
Type 'string' is not assignable to type 'never'
Code language: JavaScript (javascript)
So why do we need the never
type in the first place?
Since the never
type has zero value, you can use it to denote an impossibility in the type system.
For example, you may have an intersection type that can be both a string and a number at the same time, which is impossible:
type Alphanumeric = string & number; // never
Code language: JavaScript (javascript)
Therefore, the TypeScript compiler infers the type of Alphanumeric
as never
.
This is because string
and number
are mutually exclusive. In other words, a value cannot be both a string
and a number
simultaneously.
Typically, you use the never
type to represent the return type of a function that never returns the control to the caller. For example, a function that always throws an error:
function raiseError(message: string): never {
throw new Error(message);
}
Code language: TypeScript (typescript)
Please do not confuse with functions that return void but still return the control to the caller.
If you have a function that contains an indefinite loop, its return type should be never
. For example:
function forever(): never {
while (true) {}
}
Code language: TypeScript (typescript)
In this example, the type of the return type of the forever()
function is never
.
The TypeScript never example
Let’s take an example of using the never
type:
type Role = 'admin' | 'user';
const authorize = (role: Role): string => {
switch (role) {
case 'admin':
return 'You can do anything';
case 'user':
return 'You can do something';
default:
// never reach here util we add a new role
const _unreachable: never = role;
throw new Error(`Invalid role: ${_unreachable}`);
}
};
console.log(authorize('admin'));
Code language: JavaScript (javascript)
How it works.
Step 1. Define a type Role
that can be either a string 'admin'
or 'user'
:
type Role = 'admin' | 'user';
Code language: JavaScript (javascript)
Step 2. Create the authorize()
function that accepts a value of the Role
type and returns a string:
const authorize = (role: Role): string => {
switch (role) {
case 'admin':
return 'You can do anything';
case 'user':
return 'You can do something';
default:
// never reach here util we add a new role
const _unreachable: never = role;
throw new Error(`Invalid role: ${_unreachable}`);
}
};
Code language: JavaScript (javascript)
How it works.
First, use the switch statement to return a corresponding string if the role is admin
or user
.
Second, define a variable called _unreachable
with the type never
and assign the role
to it. Also, throw an error in the default
branch because the execution will never reach the default
branch.
Why do we handle the default
case?
The reason is that if we add a new value to the Role
type and forget to add a logic to handle the new role, TypeScript will issue an error:
type Role = 'admin' | 'user' | 'guest';
Code language: JavaScript (javascript)
In this case, we add the 'guest'
to the Role
type.
And TypeScript issues the following error:
Type 'string' is not assignable to type 'never'.ts(2322)
Code language: JavaScript (javascript)
This is because the value of the role in the default
branch now becomes the string 'guest'
and you cannot assign a string value to a variable with the type never
.
To fix this, you need to create a new case
branch to handle the new role:
const authorize = (role: Role): string => {
switch (role) {
case 'admin':
return 'You can do anything';
case 'user':
return 'You can do something';
case 'guest':
return 'You can do nothing';
default:
// never reach here util we add a new role
const _unreachable: never = role;
throw new Error(`Invalid role: ${_unreachable}`);
}
};
Code language: JavaScript (javascript)
To make it more concise, we can define a function with the return type never and use it in the default
branch:
type Role = 'admin' | 'user' | 'guest';
const unknownRole = (role: never): never => {
throw new Error(`Invalid role: ${role}`);
};
const authorize = (role: Role): string => {
switch (role) {
case 'admin':
return 'You can do anything';
case 'user':
return 'You can do something';
case 'guest':
return 'You can do nothing';
default:
// never reach here util we add a new role
return unknownRole(role);
}
};
console.log(authorize('admin'));
Code language: TypeScript (typescript)
Summary
- Use the
never
type that holds no value, denoting an impossibility in the type system.