Background: Conditional Types

Conditional types allow you to define types that express different types based on the input type:

type Input = string;
type Output = Input extends string ? string : number;

In their simplest form, conditional types don’t seem very useful because if you know how to read, you can statically determine that Output will always be of type string!

This is why you’ll almost always see conditional types used in conjunction with generics. If we apply generics to the previous example, you’ll start to see the utility of conditional types:

type Output<Input> = Input extends string ? string : number;

Here, Output will default to being a number type, but will express a string type if the input to the generic is also a string.

The infer Keyword

In TypeScript 2.8, support for the infer keyword was added to conditional types. When you declare infer T, TypeScript will infer the type of T and then allow you to reference that inferred type in the rest of the conditional. In practice, this allows you to unwrap types from wrapped types and use them in conditional types. For example, we can exploit this to write a naive implementation for the Awaited type:

type Awaited<T> = T extends Promise<infer U>
  ? U // T is of type `Promise`, return the wrapped type
  : T; // T is not of type `Promise`, return it as-is

type Input = Promise<string>;
type Output = Awaited<Input>; // Will always be of type `string`

Open in Playground

Let’s unpack what’s happening here. Awaited is a generic type that accepts a single type parameter T. In the guts, we use a conditional type to check if T is of type Promise or not. If it is not, we simply return T. Because we want to unwrap the type inside of Promise, we use infer U to instruct TypeScript to infer the type and store it in the type variable U, which we then return in the truthy path of the conditional type.

Additional Resources