The infer keyword extracts types within conditional types. Here's how to use it for powerful type manipulation.
Basic Inference#
1// Infer return type of function
2type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
3
4function greet(name: string): string {
5 return `Hello, ${name}`;
6}
7
8type GreetReturn = ReturnType<typeof greet>; // string
9
10// Infer parameter types
11type Parameters<T> = T extends (...args: infer P) => any ? P : never;
12
13type GreetParams = Parameters<typeof greet>; // [name: string]
14
15// Infer first parameter
16type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any
17 ? F
18 : never;
19
20type FirstGreetArg = FirstArg<typeof greet>; // stringArray and Tuple Inference#
1// Infer array element type
2type ElementType<T> = T extends (infer E)[] ? E : never;
3
4type StringArrayElement = ElementType<string[]>; // string
5type NumberArrayElement = ElementType<number[]>; // number
6
7// Infer tuple elements
8type First<T> = T extends [infer F, ...any[]] ? F : never;
9type Last<T> = T extends [...any[], infer L] ? L : never;
10type Tail<T> = T extends [any, ...infer R] ? R : never;
11type Init<T> = T extends [...infer I, any] ? I : never;
12
13type Tuple = [string, number, boolean];
14
15type TupleFirst = First<Tuple>; // string
16type TupleLast = Last<Tuple>; // boolean
17type TupleTail = Tail<Tuple>; // [number, boolean]
18type TupleInit = Init<Tuple>; // [string, number]
19
20// Infer tuple length
21type Length<T extends any[]> = T extends { length: infer L } ? L : never;
22
23type TupleLength = Length<Tuple>; // 3Promise Unwrapping#
1// Unwrap single Promise
2type Awaited<T> = T extends Promise<infer U> ? U : T;
3
4type P1 = Awaited<Promise<string>>; // string
5type P2 = Awaited<string>; // string
6
7// Recursively unwrap nested Promises
8type DeepAwaited<T> = T extends Promise<infer U>
9 ? DeepAwaited<U>
10 : T;
11
12type Nested = Promise<Promise<Promise<number>>>;
13type Unwrapped = DeepAwaited<Nested>; // number
14
15// Await all values in tuple
16type AwaitAll<T extends Promise<any>[]> = {
17 [K in keyof T]: T[K] extends Promise<infer U> ? U : never;
18};
19
20type Promises = [Promise<string>, Promise<number>, Promise<boolean>];
21type Values = AwaitAll<Promises>; // [string, number, boolean]Function Type Manipulation#
1// Extract this type
2type ThisType<T> = T extends (this: infer U, ...args: any[]) => any
3 ? U
4 : never;
5
6function handler(this: HTMLElement, event: Event) {}
7
8type HandlerThis = ThisType<typeof handler>; // HTMLElement
9
10// Infer constructor type
11type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;
12
13class User {
14 constructor(public name: string) {}
15}
16
17type UserInstance = InstanceType<typeof User>; // User
18
19// Constructor parameters
20type ConstructorParameters<T> = T extends new (...args: infer P) => any
21 ? P
22 : never;
23
24type UserParams = ConstructorParameters<typeof User>; // [name: string]Object Type Inference#
1// Extract property type
2type PropType<T, K extends keyof T> = T extends { [P in K]: infer V }
3 ? V
4 : never;
5
6interface User {
7 id: string;
8 name: string;
9 age: number;
10}
11
12type UserName = PropType<User, 'name'>; // string
13
14// Infer value types from object
15type ValueOf<T> = T extends { [K in keyof T]: infer V } ? V : never;
16
17type UserValues = ValueOf<User>; // string | number
18
19// Infer from getter
20type GetterReturn<T> = T extends { get: () => infer R } ? R : never;
21
22interface Store {
23 get: () => User;
24 set: (value: User) => void;
25}
26
27type StoreValue = GetterReturn<Store>; // UserString Literal Inference#
1// Parse template literal
2type ParseRoute<T> = T extends `${infer Start}:${infer Param}/${infer Rest}`
3 ? [Start, Param, ...ParseRoute<`/${Rest}`>]
4 : T extends `${infer Start}:${infer Param}`
5 ? [Start, Param]
6 : [];
7
8type Route = '/users/:userId/posts/:postId';
9type Parsed = ParseRoute<Route>;
10// ['', 'userId', '', 'postId']
11
12// Extract route parameters
13type ExtractParams<T> = T extends `${string}:${infer Param}/${infer Rest}`
14 ? Param | ExtractParams<`/${Rest}`>
15 : T extends `${string}:${infer Param}`
16 ? Param
17 : never;
18
19type RouteParams = ExtractParams<Route>;
20// 'userId' | 'postId'
21
22// Split string
23type Split<S extends string, D extends string> =
24 S extends `${infer Head}${D}${infer Tail}`
25 ? [Head, ...Split<Tail, D>]
26 : [S];
27
28type Parts = Split<'a.b.c', '.'>; // ['a', 'b', 'c']Complex Inference Patterns#
1// Infer event handler types
2type EventHandlers<T> = {
3 [K in keyof T as K extends `on${infer E}` ? Lowercase<E> : never]:
4 T[K] extends ((event: infer E) => any) | null | undefined
5 ? E
6 : never;
7};
8
9interface ButtonProps {
10 onClick: (event: MouseEvent) => void;
11 onFocus: (event: FocusEvent) => void;
12 disabled: boolean;
13}
14
15type ButtonEvents = EventHandlers<ButtonProps>;
16// { click: MouseEvent; focus: FocusEvent }
17
18// Infer state and actions from reducer
19type InferState<T> = T extends (state: infer S, action: any) => any
20 ? S
21 : never;
22
23type InferAction<T> = T extends (state: any, action: infer A) => any
24 ? A
25 : never;
26
27type State = { count: number };
28type Action = { type: 'increment' } | { type: 'decrement' };
29
30function reducer(state: State, action: Action): State {
31 switch (action.type) {
32 case 'increment':
33 return { count: state.count + 1 };
34 case 'decrement':
35 return { count: state.count - 1 };
36 }
37}
38
39type ReducerState = InferState<typeof reducer>; // State
40type ReducerAction = InferAction<typeof reducer>; // Action
41
42// Infer API response type
43type ApiResponse<T> =
44 T extends (...args: any[]) => Promise<infer R>
45 ? R extends { data: infer D }
46 ? D
47 : R
48 : never;
49
50async function fetchUser(id: string): Promise<{ data: User; status: number }> {
51 return { data: { id, name: 'John', age: 30 }, status: 200 };
52}
53
54type FetchUserResponse = ApiResponse<typeof fetchUser>; // UserMultiple Infer Positions#
1// Infer both key and value
2type Entries<T> = {
3 [K in keyof T]: [K, T[K]];
4}[keyof T];
5
6type UserEntries = Entries<User>;
7// ['id', string] | ['name', string] | ['age', number]
8
9// Infer function with multiple params
10type Curry<T> = T extends (a: infer A, b: infer B, c: infer C) => infer R
11 ? (a: A) => (b: B) => (c: C) => R
12 : T extends (a: infer A, b: infer B) => infer R
13 ? (a: A) => (b: B) => R
14 : T extends (a: infer A) => infer R
15 ? (a: A) => R
16 : T;
17
18function add(a: number, b: number, c: number): number {
19 return a + b + c;
20}
21
22type CurriedAdd = Curry<typeof add>;
23// (a: number) => (b: number) => (c: number) => numberPractical Examples#
1// Extract component props
2type ComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
3
4const Button: React.FC<{ label: string; onClick: () => void }> = (props) => (
5 <button onClick={props.onClick}>{props.label}</button>
6);
7
8type ButtonProps = ComponentProps<typeof Button>;
9// { label: string; onClick: () => void }
10
11// Infer hook return type
12type HookReturn<T> = T extends () => infer R ? R : never;
13
14function useCounter() {
15 const [count, setCount] = useState(0);
16 return { count, increment: () => setCount(c => c + 1) };
17}
18
19type CounterHook = HookReturn<typeof useCounter>;
20// { count: number; increment: () => void }Best Practices#
Usage:
✓ Use infer in conditional types only
✓ Position infer at extraction points
✓ Combine with recursion for complex types
✓ Provide fallback types
Readability:
✓ Name inferred types descriptively
✓ Break complex types into smaller parts
✓ Document inference behavior
✓ Use helper types for common patterns
Performance:
✓ Avoid deeply nested inference
✓ Use constraints to narrow scope
✓ Cache complex inferred types
✓ Test type computation time
Conclusion#
The infer keyword enables powerful type extraction within conditional types. Use it to unwrap Promises, extract function parameters, parse template literals, and build flexible utility types. Combined with recursive types and mapped types, infer unlocks advanced type-level programming in TypeScript.