Hint
T extends U ? X : Y — use infer inside extends to capture and name a type for use in X or Y
Conditional types are TypeScript's equivalent of if-else for types. Combined with infer, they let you extract type information from complex types.
// Unwrap a Promise
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// If T is a Promise, extract what it resolves to and recurse
// Otherwise return T as-is
// Extract the element type of an array
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Elem = ArrayElement<string[]>; // string
// Extract return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Extract the first parameter
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any
? F
: never;
type FP = FirstParam<(a: string, b: number) => void>; // string
Multiple infer in one conditional:
// Extract both key and value types from a Map
type MapTypes<T> = T extends Map<infer K, infer V>
? { key: K; value: V }
: never;
type UserMap = MapTypes<Map<string, User>>;
// { key: string; value: User }
// Flatten one level of nesting
type Flatten<T> = T extends Array<infer E>
? E extends Array<infer Inner> ? Inner : E
: T;
type Flat = Flatten<number[][]>; // number[]
infer only works inside extends clauses of conditional types. Think of it as "if this type matches this pattern, capture the matched part as a new type variable".