How does TypeScript's "infer" work?

Question:

I know it was introduced in the 2.8 version of the language (see here ), but despite having read these release notes, I still couldn't understand how infer works.

  • How it works?
  • What is your real objective?

Answer:

infer is used in conjunction with type conditions in TypeScript because it will try to infer a type, and if not, something must happen. So it must always be in a conditional type .

Basically, infer will "create" a new type, whose name will come in front of you:

infer NewType ? [[conseguiu-inferir]] : [[não-conseguiu-inferir]]

Where NewType will be the type that is inferred. In this sense, we will be able to use it in the "typical expression" that is positioned in [[conseguiu-inferir]] .

An example:

type GetArrayMemberType<T> = T extends Array<infer Member> ? Member : T;

type A = GetArrayMemberType<string[]>; // A inferido para `string`
type B = GetArrayMemberType<string[][]>; // B inferido para `string[]`
type C = GetArrayMemberType<number>; // C inferido para `number`

Link to the playground.

When conditional types are used in conjunction with infer (which must occupy the first operand of the "ternary conditional"), the type of the second operand (which is usually composed from the inferred type) is returned. If type inference is not possible, the third operand will be returned.

Think about this need for infer to be associated with a conditional type relating the TypeScript type system to a purely functional mini programming language. That's because every TypeScript type must always be something (just like an expression in functional programming languages). And what if the type couldn't be inferred and we didn't provide an " else clause"? The resulting type should be what?

In short, just as a Haskell if always needs an else , a TypeScript conditional type also always needs an else (which is located in the third term of the ternary construction). This, coupled with infer , guarantees that we will always have a valid type, even when inference is not possible.

See this structure:

//|             Primeiro operando. O `infer` deve ficar nele.
//|                                │
//|                                │           ╔ Nome que será atribuído ao tipo inferido pelo `infer`.
//|                               ┌┴───────────║────┐
//|                               │           ╔╩═══╗│
1 | type UnpackArr<T> = T extends Array<infer Member>
//|    ┌ O segundo operando da condição será retornado caso a inferência tenha sido possível.
//|    │ Geralmente, esse tipo é composto a partir daquele que foi inferido (no caso, `Member`).
//|   ┌┴─────────────┐
2 | ? { type: Member }
//|   ┌ O terceiro operando da condição será retronado caso a inferência não tenha sido possível.
//|   │ Neste caso, só estamos retornando o tipo passado (`T`).
3 | : T;

Note that a name must always be placed after the infer . It is the name that will be given to the type that will be inferred. The name can be used in the second operand of the type condition.

What is your real objective?

The goal is to be able to infer types. Most often it is used to "unwrap" a type, getting the value of an "inner" type.

For example, as of the current TypeScript version for when I write this answer, the awaited keyword does not yet exist in TypeScript. So the only way to get the type T of a Promise<T> is from infer . Look:

type Awaited<T> = T extends Promise<infer Member> ? Member : T;

type A = Awaited<Promise<string>>; // A inferido `string`.
type B = Awaited<Promise<{ name: string, age: number }>>; // B inferido `{ name: string, age: number }`.

Link to the playground.

In the future, awaited Promise<T> (a construct that doesn't yet exist in TypeScript) will return type T , but for now the only way to achieve this is to use type conditionals in conjunction with infer .

This is generally more useful for library authors or anything that requires a little more complex typing.

Scroll to Top