All Articles

When 'as never' Is The Only Thing That Works

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.

as never is very occasionally needed in TypeScript. Let's look at an example where it's necessary.

Let's imagine we want to format some input based on its typeof. We first create a formatters object that maps typeof to a formatting function:

ts
const formatters = {
string: (input: string) => input.toUpperCase(),
number: (input: number) => input.toFixed(2),
boolean: (input: boolean) => (input ? "true" : "false"),
};

Next, we create a format function that takes an input of string | number | boolean and formats it based on its typeof.

ts
const format = (input: string | number | boolean) => {
// We need to cast here because TypeScript isn't quite smart
// enough to know that `typeof input` can only be
// 'string' | 'number' | 'boolean'
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
 
return formatter(input);
Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'.2345Argument of type 'string | number | boolean' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'.
};

But there's a strange error:

Type 'string' is not assignable to type 'never'.

What's going on here?

Unions Of Functions With Incompatible Params

Let's take a deeper look at the type of formatter inside our format function:

ts
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
const formatter: ((input: string) => string) | ((input: number) => string) | ((input: boolean) => "true" | "false")
 
return formatter(input);
};

As you can see, it resolves to a union of functions, each with a different parameter. One function takes a string, another a number, and the last a boolean.

How could we possibly call this function with a string and a number at the same time? We can't. So, the function actually resolves to:

ts
type Func = (input: never) => string;

Shouldn't The Parameters Resolve To A Union?

You might be thinking, "Shouldn't the parameters resolve to a union of string | number | boolean?"

This doesn't work, because calling formatters.string with a number is unsafe. Calling formatters.boolean with a number is unsafe.

So, never is the only type that makes sense.

How Do We Fix This?

We happen to know that the logic of this function is sound. We know that formatters[inputType] will resolve to the correct type.

So, we can use an as never:

ts
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
 
return formatter(input as never);
};

This forces TypeScript to consider input as the type of never - which is, of course, assignable to formatter's parameter of never.

Wouldn't as any Work?

Amazingly, any doesn't work here:

ts
const format = (input: string | number | boolean) => {
const inputType = typeof input as
| "string"
| "number"
| "boolean";
const formatter = formatters[inputType];
 
return formatter(input as any);
Argument of type 'any' is not assignable to parameter of type 'never'.2345Argument of type 'any' is not assignable to parameter of type 'never'.
};

It results in a horrendous error:

Argument of type 'any' is not assignable to parameter of type 'never'.

So, as never is the only way to go here.

Matt's signature

Share this article with your friends

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock

No, TypeScript Types Don't Exist At Runtime

Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.

Matt Pocock
Matt Pocock

Deriving vs Decoupling: When NOT To Be A TypeScript Wizard

In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.

Matt Pocock
Matt Pocock

NoInfer: TypeScript 5.4's New Utility Type

Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.

Matt Pocock
Matt Pocock