The infer keyword in TypeScript is a powerful tool for type inference, especially within conditional types. It allows you to declare a type variable within the extends clause of a conditional type and then use that variable in the true branch of the conditional. This is incredibly useful for extracting specific parts of existing types.
Let’s see infer in action. Imagine you have a type representing a function’s return type, and you want to create a new type that only extracts that return type.
// Original type representing a function
type MyFunction = (name: string) => number;
// Conditional type using infer to extract the return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Now, let's use it
type MyReturnType = ReturnType<MyFunction>; // This will be 'number'
// Another example: extracting the element type from an array
type ElementType<T> = T extends (infer E)[] ? E : never;
type MyArray = string[];
type MyElementType = ElementType<MyArray>; // This will be 'string'
The magic happens in the infer R and infer E parts. When T extends (...args: any[]) => infer R is evaluated, TypeScript tries to match T against the function signature. If it matches, it infers the return type into the R variable. Similarly, T extends (infer E)[] tries to match T against an array type, inferring the element type into E.
The primary problem infer solves is the inability to directly "destructure" or "pull out" parts of a type within a conditional type without infer. Before infer, you’d often have to resort to more complex, sometimes brittle, type manipulation or rely on built-in utility types that already incorporated this logic. infer gives you the explicit control to define what you want to extract and how you want to use it.
Consider a more advanced scenario: extracting the type of the first argument of a function.
type FirstArg<T> = T extends (arg: infer A, ...rest: any[]) => any ? A : never;
type MyFuncWithArgs = (name: string, age: number) => boolean;
type FirstArgumentType = FirstArg<MyFuncWithArgs>; // This will be 'string'
Here, infer A captures the type of the first parameter in the function signature. The ...rest: any[] part is crucial for ensuring that the type T is indeed a function with at least one argument, and any[] allows for any number of subsequent arguments without affecting the inference of A.
The infer keyword is particularly elegant because it declares the type variable locally within the conditional type. You don’t need to define it beforehand. It’s defined implicitly when the pattern matches. This makes it feel very declarative, like you’re saying, "if T looks like this pattern, then give me the part I’ve labeled infer X."
When you use infer with multiple generic parameters in a function type, you can extract them individually. For example, to get the this parameter type of a method:
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : never;
const method = function(this: { id: number }, value: string) { return value.length; };
type MethodThisType = ThisParameterType<typeof method>; // This will be '{ id: number; }'
In this case, infer U is specifically placed to capture the type of the this parameter, which is the first parameter in a method’s signature when explicitly defined.
The most surprising thing about infer is how it allows you to reconstruct types based on what you extract. It’s not just about observation; it’s about transformation. You can take an extracted type and immediately use it in a new type definition within the same conditional.
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type Foo = Promise<string>;
type Bar = UnpackPromise<Foo>; // 'string'
type Baz = UnpackPromise<number>; // 'number'
Here, if T is a Promise of something, infer U extracts that "something." Then, the true branch returns U. If T is not a Promise, the false branch simply returns T itself, making UnpackPromise a type that unwraps a Promise or leaves other types as they are.
The next concept you’ll likely encounter is using infer in conjunction with mapped types and recursive conditional types for more complex type transformations.