Post

Understanding the infer keyword in TypeScript

Photo by Chris Ried on Unsplash

The infer keyword was always confusing to me, despite reading up on it multiple times. I’ll try to explain it clearly here.

Theinfer keyword can only be used in conditional types in order to declaratively introduce a new generic type variable. This is used for many default TypeScript utility types such as Parameters<Type> to get the parameter types of a function and ReturnType<Type>to get the return types of a function.

For example, the below type checks if T is a function, and returns the return type of that function.

type GetReturnType<T> = T extends (...args: never[]) => infer Return
? Return
: never;

type Num = GetReturnType<() => number>;
// ^ type number

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
// ^ type boolean[]

In other words, we are able to declare the type of the return type of the function without explicitly defining a generic type variable.

Instead of forcing the return type of the function to be a certain type, we are asking TypeScript to define it as a type variable with the name Return which allows us to use it in other places.

What this solves

Some of you might think, why not just declare a generic type for the return value then? For example:

type GetReturnType<T, Return> = T extends (...args: never[]) => Return
? Return
: never;

type Num = GetReturnType<() => number, number>;
// ^ type number ^ if we don't fill in this we get an error

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[], boolean[]>;
// ^ type boolean[] ^ if we don't fill in this we get an error

However, this means that you have to define another generic type for the return value, which defeats the purpose of the type definition.

What about this?

How about not having the infer keyword at all? Why is it necessary?

type GetReturnType<T> = T extends (...args: never[]) => Return
? Return
: never;

// error, cannot find name 'Return'

This doesn’t work because we need to declare our type variables explicitly. TypeScript doesn’t know where the type variable Return comes from.

Basically, we can think of it as declaring a name for the type, with the type value being the actual type at that location.

More Examples

I was pretty confused when I first tried to understand this, so here are more examples for understanding.

Flatten

The below type returns the inner elements if it is an array, otherwise it returns the type itself:

type Flatten<T> = T extends Array<infer Item> ? Item : T;

type Num = Flatten<number[]>
// ^ type number

type OriginalNumber = Flatten<number>
// ^ type number

type Bool = Flatten<boolean>
// ^ type boolean

It basically declares the elements of an array as a type, and returns it if it exists. If not, it returns the T that does not extend it.

GetParameterType

The below type gets the parameters of a function type. I took it from the actual Paramter<T> type implementation from their utility types and made it simpler for better understanding.

type GetParameterType<T> = T extends (...args: infer Param) => any 
? Param
: never;

type Num = GetParameterType<(n: number=> number>;
// ^ number

type Bool = GetParameterType<(n: boolean=> boolean[]>;
// ^ boolean

The type of the parameters of the function are declared as the type Param using the infer keyword.

Small Things to Note

There are certain things to take note which makes sense if you think about it:

  1. The inferred value can only be used in the true branch, because in the case where T does not extend the type, the inferred value would not exist.
type Flatten<T> = T extends Array<infer Item> ? Item : T; // this works

type Flatten<T> = T extends Array<infer Item> ? T : Item; // cannot find name 'Item'.

2. infer is always used within the extends clause and not anywhere else.

Summary

The infer keyword allows us to define types in the context of conditional types so that we can use them elsewhere. It is used extensively in utility types and are very powerful.

References

Documentation - Conditional Types

This post is licensed under CC BY 4.0 by the author.