infer

Declares a placeholder type variable inside a conditional type's extends clause to capture part of a type.

Signature
Type extends Pattern<infer Inferred> ? Inferred : Fallback
Type extends Pattern<infer Inferred extends Constraint> ? Inferred : Fallback
  • Added in TypeScript 2.8

Parameters

ParameterTypeDescription
$Inferredtype variable nameThe name being declared. Reusing one name in multiple covariant positions produces a union; reusing it in contravariant positions produces an intersection.
$ConstraintoptionaltypeAn optional extends constraint on the inferred variable, added in TypeScript 4.7. The branch is only taken if the captured type satisfies it.

Return value

The captured type, available only inside the true branchNothing on its own. It binds a name to whatever type occupies that position when the pattern matches, and that name is usable only in the true branch of the enclosing conditional.

Errors

Writing it anywhere other than the extends clause of a conditional type is an error: "'infer' declarations are only permitted in the 'extends' clause of a conditional type".

Changelog

  • PHP 2.8Introduced together with conditional types; ReturnType, InstanceType and Parameters in lib.d.ts are all built on it.
  • PHP 4.7infer declarations may carry an extends constraint, so a branch can require the captured type to match a shape before it is taken.
  • PHP 4.8infer X extends string | number | bigint now infers literal types where previously the constraint forced widening.

infer is how a conditional type takes a type apart. Instead of asking "is T an array?" and then separately digging out the element type, you write the shape you expect with a hole in it — T extends Array<infer Item> ? Item : T — and the compiler fills the hole for you.

Every unwrapping utility in lib.d.ts is this pattern. ReturnType is T extends (...args: never[]) => infer R ? R : never. Awaited, Parameters and InstanceType are three more lines of the same shape.

When to reach for it

When a type you need is already inside a type you have. Rather than exporting a separate ElementType alias next to every array, or duplicating a callback's argument list, match the surrounding shape and capture the piece.

What trips people up

It is legal only in the extends clause of a conditional type, and the captured name exists only in the true branch. Referencing it in the false branch is an error.

Repeating one name is not a mistake — it combines. Two covariant positions produce a union, two contravariant (parameter) positions produce an intersection, which is why inferring from an overloaded function silently uses only the last signature.

Example

Example · typescript
type ElementType<T> = T extends readonly (infer Item)[] ? Item : T;

type A = ElementType<number[]>;            // number
type B = ElementType<readonly string[]>;   // string
type C = ElementType<boolean>;             // boolean (no match, falls through)

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

type D = MyReturnType<() => Date>;              // Date
type E = MyReturnType<(x: string) => string[]>; // string[]
type F = MyReturnType<string>;                  // never

// type Bad = infer X; // error: only permitted in an extends clause

When to use it

  • Write a hand-rolled Awaited to unwrap nested promise types
  • Pull the props type out of a component type without exporting it
  • Capture the element type of a readonly tuple to build a mapped result

Frequently asked questions

Where can I use infer?

Only inside the extends clause of a conditional type, and the name it declares is only visible in the true branch. Anywhere else the compiler reports "'infer' declarations are only permitted in the 'extends' clause of a conditional type".

How do I get the type a promise resolves to?

Use the built-in Awaited<T>, or write it yourself as T extends Promise<infer U> ? U : T. Make it recursive - T extends Promise<infer U> ? MyAwaited<U> : T - if you need to unwrap nested promises.

Why does ReturnType give the wrong type for my overloaded function?

Inference from an overloaded function type only considers the last signature, which is usually the broad implementation-facing catch-all. Reorder the overloads so the one you want is last, or infer from a narrowed function type instead.

More examples

Nested pattern matching, the way Awaited works

Each branch tries a different shape and the first match wins, so T4 only peels one layer - the array branch matched before the promise branch was reached. DeepUnpack shows the recursive form: a conditional type may reference itself, which is exactly how the built-in Awaited flattens nested promises.

Example · typescript
type Unpacked<T> =
  T extends (infer U)[] ? U :
  T extends (...args: never[]) => infer U ? U :
  T extends Promise<infer U> ? U :
  T;

type T0 = Unpacked<string>;           // string
type T1 = Unpacked<string[]>;         // string
type T2 = Unpacked<() => string>;     // string
type T3 = Unpacked<Promise<string>>;  // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>

// recurse to unwrap all the way down
type DeepUnpack<T> = T extends Promise<infer U> ? DeepUnpack<U> : T;
type T5 = DeepUnpack<Promise<Promise<number>>>; // number

Repeating a name, and constraining it

Position decides how repeated captures combine: property types are covariant and union together, while function parameters are contravariant and intersect - which is how string & number, an effectively impossible type, appears. The last pair shows the 4.7 constraint form, where the true branch is only taken when the captured head actually is a string.

Example · typescript
// same name in two covariant positions -> union
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type T10 = Foo<{ a: string; b: string }>; // string
type T11 = Foo<{ a: string; b: number }>; // string | number

// same name in two contravariant positions -> intersection
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
type T20 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string & number

// TypeScript 4.7: constrain the inferred variable
type FirstIfString<T> = T extends [infer H extends string, ...unknown[]] ? H : never;
type T30 = FirstIfString<["go", 1]>; // "go"
type T31 = FirstIfString<[1, "go"]>; // never

Discussion

  • Be the first to comment on this lesson.