Back to Blog
TypeScriptinferConditional TypesType Extraction

TypeScript infer Keyword Guide

Master the TypeScript infer keyword for extracting types within conditional type expressions.

B
Bootspring Team
Engineering
April 9, 2019
7 min read

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

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

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

Object 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 | string

Tuple 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>>; // HTMLInputElement

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

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

Share this article

Help spread the word about Bootspring