Back to Blog
TypeScriptInferTypesAdvanced

TypeScript Infer Keyword Deep Dive

Master the TypeScript infer keyword. From basic inference to complex type extraction to practical patterns.

B
Bootspring Team
Engineering
November 16, 2021
7 min read

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>; // string

Array 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>; // 3

Promise 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>; // User

String 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>; // User

Multiple 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) => number

Practical 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.

Share this article

Help spread the word about Bootspring