Concept 7 of 12
infer
`infer` is pattern matching. You describe the shape you expect, put `infer X` where the interesting part sits, and the compiler solves for `X`. Most of the standard library’s extraction helpers are one conditional with one `infer` in the right place.
It works anywhere a type can appear
type Unwrap<T> = T extends Promise<infer Inner> ? Inner : T type Element<T> = T extends (infer E)[] ? E : never type Returns<T> = T extends (...a: never[]) => infer R ? R : never type Head<T> = T extends [infer H, ...unknown[]] ? H : never type Rest<S> = S extends `${string}-${infer R}` ? R : never
Promises, arrays, function returns, tuple positions, string patterns — the mechanism is identical every time. Only the surrounding shape changes, which is why learning one of these teaches you all of them.
Constraining what gets bound
// Without the constraint, F is unknown and cannot be // interpolated into a template literal. type First<T> = T extends [infer F extends string, ...unknown[]] ? `${F}!` : never
An `infer` can carry its own `extends` clause, which narrows what it binds and lets you use the result where a specific kind of type is required. Without it you often get `unknown`, and the error appears one line later than the cause.
The common wrong answer
// Intent: get the return type of any function. type Returns<T> = T extends () => infer R ? R : never Returns<() => string> // string Returns<(a: number) => string> // never — pattern demands zero parameters // Fixed: never[] in parameter position accepts any signature, // because parameters are checked contravariantly. type Returns2<T> = T extends (...a: never[]) => infer R ? R : never
The pattern has to be as permissive as the inputs you want to match. A zero-parameter function type only matches zero-parameter functions, so everything else silently falls into the false branch.
Takeaway
Describe the shape, mark the hole with `infer`, and let the compiler fill it. If nothing matches, the pattern is stricter than the input — not the other way round.