The infer keyword enables type extraction within conditional types. Here's how to use it for powerful type transformations.
Basic Syntax#
1// Infer return type of function
2type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
3
4type Fn = () => string;
5type Result = ReturnType<Fn>; // string
6
7// Infer parameter types
8type Parameters<T> = T extends (...args: infer P) => any ? P : never;
9
10type Fn2 = (a: string, b: number) => void;
11type Params = Parameters<Fn2>; // [string, number]Array Element Type#
1// Extract element type from array
2type ArrayElement<T> = T extends (infer E)[] ? E : never;
3
4type Numbers = ArrayElement<number[]>; // number
5type Strings = ArrayElement<string[]>; // string
6type Mixed = ArrayElement<(string | number)[]>; // string | number
7
8// Also works with readonly arrays
9type ReadonlyElement<T> = T extends readonly (infer E)[] ? E : never;
10
11type Items = ReadonlyElement<readonly string[]>; // stringPromise Unwrapping#
1// Extract type from Promise
2type Awaited<T> = T extends Promise<infer U> ? U : T;
3
4type A = Awaited<Promise<string>>; // string
5type B = Awaited<Promise<number>>; // number
6type C = Awaited<string>; // string (non-promise passthrough)
7
8// Deep unwrap nested promises
9type DeepAwaited<T> = T extends Promise<infer U>
10 ? DeepAwaited<U>
11 : T;
12
13type Nested = DeepAwaited<Promise<Promise<Promise<string>>>>; // stringFunction Analysis#
1// First parameter
2type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any
3 ? F
4 : never;
5
6type First = FirstParam<(a: string, b: number) => void>; // string
7
8// Last parameter
9type LastParam<T> = T extends (...args: [...any[], infer L]) => any
10 ? L
11 : never;
12
13type Last = LastParam<(a: string, b: number, c: boolean) => void>; // boolean
14
15// Constructor parameters
16type ConstructorParams<T> = T extends new (...args: infer P) => any
17 ? P
18 : never;
19
20class User {
21 constructor(name: string, age: number) {}
22}
23
24type UserParams = ConstructorParams<typeof User>; // [string, number]
25
26// Instance type
27type InstanceType<T> = T extends new (...args: any[]) => infer R
28 ? R
29 : never;
30
31type UserInstance = InstanceType<typeof User>; // UserObject Property Types#
1// Extract property type
2type PropertyType<T, K extends keyof T> = T extends { [P in K]: infer V }
3 ? V
4 : never;
5
6interface User {
7 id: number;
8 name: string;
9}
10
11type IdType = PropertyType<User, 'id'>; // number
12
13// Extract all property types
14type ValueOf<T> = T extends { [K: string]: infer V } ? V : never;
15
16type UserValues = ValueOf<User>; // number | stringTuple Manipulation#
1// First element
2type First<T> = T extends [infer F, ...any[]] ? F : never;
3
4type A = First<[string, number, boolean]>; // string
5
6// Last element
7type Last<T> = T extends [...any[], infer L] ? L : never;
8
9type B = Last<[string, number, boolean]>; // boolean
10
11// Rest elements
12type Tail<T> = T extends [any, ...infer R] ? R : never;
13
14type C = Tail<[string, number, boolean]>; // [number, boolean]
15
16// Init (all but last)
17type Init<T> = T extends [...infer I, any] ? I : never;
18
19type D = Init<[string, number, boolean]>; // [string, number]
20
21// Reverse tuple
22type Reverse<T extends any[]> = T extends [infer F, ...infer R]
23 ? [...Reverse<R>, F]
24 : [];
25
26type E = Reverse<[1, 2, 3]>; // [3, 2, 1]String Pattern Matching#
1// Extract route params
2type ExtractParam<T extends string> = T extends `:${infer Param}`
3 ? Param
4 : never;
5
6type A = ExtractParam<':userId'>; // 'userId'
7
8// Parse route
9type ParseRoute<T extends string> =
10 T extends `${string}/:${infer Param}/${infer Rest}`
11 ? Param | ParseRoute<`/${Rest}`>
12 : T extends `${string}/:${infer Param}`
13 ? Param
14 : never;
15
16type Params = ParseRoute<'/users/:userId/posts/:postId'>;
17// 'userId' | 'postId'
18
19// Split string
20type Split<S extends string, D extends string> =
21 S extends `${infer T}${D}${infer U}`
22 ? [T, ...Split<U, D>]
23 : [S];
24
25type Parts = Split<'a-b-c', '-'>; // ['a', 'b', 'c']React Component Props#
1// Extract props from component
2type ComponentProps<T> = T extends React.ComponentType<infer P>
3 ? P
4 : never;
5
6// Works with function and class components
7const Button: React.FC<{ onClick: () => void; label: string }> = (props) => (
8 <button onClick={props.onClick}>{props.label}</button>
9);
10
11type ButtonProps = ComponentProps<typeof Button>;
12// { onClick: () => void; label: string }
13
14// Extract ref type
15type RefType<T> = T extends React.Ref<infer R> ? R : never;
16
17type InputRef = RefType<React.Ref<HTMLInputElement>>; // HTMLInputElementEvent Handler Types#
1// Extract event type from handler
2type EventType<T> = T extends (event: infer E) => any ? E : never;
3
4type ClickHandler = (event: MouseEvent) => void;
5type ClickEvent = EventType<ClickHandler>; // MouseEvent
6
7// Extract callback argument type
8type CallbackArg<T> = T extends (arg: infer A) => any ? A : never;
9
10interface API {
11 onSuccess: (data: { id: number; name: string }) => void;
12 onError: (error: Error) => void;
13}
14
15type SuccessData = CallbackArg<API['onSuccess']>;
16// { id: number; name: string }Generic Constraints with Infer#
1// Extract generic type argument
2type ExtractGeneric<T> = T extends Array<infer E>
3 ? E
4 : T extends Set<infer E>
5 ? E
6 : T extends Map<any, infer V>
7 ? V
8 : never;
9
10type A = ExtractGeneric<Array<string>>; // string
11type B = ExtractGeneric<Set<number>>; // number
12type C = ExtractGeneric<Map<string, boolean>>; // boolean
13
14// Multiple infer positions
15type MapTypes<T> = T extends Map<infer K, infer V>
16 ? { key: K; value: V }
17 : never;
18
19type D = MapTypes<Map<string, number>>;
20// { key: string; value: number }Recursive Type Extraction#
1// Flatten nested arrays
2type Flatten<T> = T extends Array<infer U>
3 ? Flatten<U>
4 : T;
5
6type A = Flatten<number[][][]>; // number
7
8// Deep property access
9type DeepValue<T, Path extends string> =
10 Path extends `${infer Key}.${infer Rest}`
11 ? Key extends keyof T
12 ? DeepValue<T[Key], Rest>
13 : never
14 : Path extends keyof T
15 ? T[Path]
16 : never;
17
18interface Nested {
19 user: {
20 profile: {
21 name: string;
22 age: number;
23 };
24 };
25}
26
27type Name = DeepValue<Nested, 'user.profile.name'>; // stringPractical Utilities#
1// Unpack wrapped types
2type Unpack<T> =
3 T extends Promise<infer U> ? U :
4 T extends Array<infer U> ? U :
5 T extends Set<infer U> ? U :
6 T extends Map<any, infer U> ? U :
7 T extends (...args: any[]) => infer U ? U :
8 T;
9
10// Get this type of method
11type ThisType<T> = T extends (this: infer U, ...args: any[]) => any
12 ? U
13 : never;
14
15function greet(this: { name: string }) {
16 return `Hello, ${this.name}`;
17}
18
19type Context = ThisType<typeof greet>; // { name: string }
20
21// Extract non-nullable
22type NonNullableDeep<T> = T extends null | undefined
23 ? never
24 : T extends object
25 ? { [K in keyof T]: NonNullableDeep<T[K]> }
26 : T;Best Practices#
When to Use:
✓ Extracting types from complex types
✓ Building utility types
✓ Pattern matching on types
✓ Generic type analysis
Patterns:
✓ Function return/parameter types
✓ Array/tuple element types
✓ Promise unwrapping
✓ String template parsing
Tips:
✓ Use with conditional types
✓ Combine multiple infer clauses
✓ Add constraints when possible
✓ Document complex extractions
Avoid:
✗ Over-complex nested infer
✗ Unbounded recursion
✗ Missing fallback types
✗ Type-level computation abuse
Conclusion#
The infer keyword enables powerful type extraction within conditional types. Use it to extract function parameters and return types, unwrap container types like Promise and Array, parse string literals with template patterns, and build sophisticated utility types. Combined with recursive conditional types, infer enables complex type transformations while maintaining full type safety.