infer — Pattern Matching for Types
Advanced · 20 min read · ▶ live playground · ✦ checkpoint
infer lets a conditional type name a piece of the shape it matched. T extends Promise<infer Value> ? Value : never means: if T fits Promise<...>, call the inside type Value and return it.
Use it when indexed access cannot name the piece directly, but a pattern can.
infer names the blank
An infer variable is a temporary type name created inside the extends side of a conditional type. Think of it as a labeled blank in a pattern: Promise<infer Value> says, "match a Promise, and write whatever is inside into the blank named Value."
type UnwrapPromise<T> = T extends Promise<infer Value> ? Value : never;Read it with last lesson's conditional-type voice: "for some type T, if T fits Promise<Value>, return Value; otherwise return never."
UnwrapPromise<Promise<LaunchPayload>> returns the payload type. UnwrapPromise<string> would return never, because a plain string does not match the Promise<...> pattern.
infer does not inspect runtime promises. It works on types before the program runs, then erases like the rest of the type layer.
Rebuild ReturnType and Parameters
Functions have a shape too: parameters on the left, return type on the right. infer can name both pieces.
These are tiny teaching copies of the built-in utility types. Section 12.3 owns the real wrapper-code uses.
MyReturnType matches a function pattern and names the return piece Result. MyParameters matches the same kind of function pattern and names the parameter tuple Args.
A parameter tuple is a tuple type that stores a function's parameter types in order. For BuildReceipt, ReceiptArgs is [email: string, plan: "free" | "pro"], so the rest parameter in buildReceipt(...args: ReceiptArgs) gets the same call shape.
infer can have a constraint
Sometimes you want to name a piece only if that piece also fits a smaller shape. An inferred constraint is infer Name extends Shape, which means "fill this blank, but only with a type assignable to Shape."
The first two union members have payload objects with a string kind, so Payload is allowed and Payload["kind"] gives "email" | "sms". The legacy member has payload: "legacy", which does not fit { kind: string }, so that branch becomes never.
This is still the same conditional-type machine from 10.3: distribute over the union, match each member, return the piece you want, and let never disappear.
infer has one legal home
infer only works inside the pattern part of a conditional type. You cannot use it as a free-standing type name.
type Bad = infer Value;→ playground.ts:1:12 - error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
Stop before the chain turns clever
Because infer can name nested pieces, it can tempt you into writing a whole puzzle in one alias. The checker may understand it; the next person may not.
The aliases are deliberately split:
HandlerRequest<T>extracts the request.HandlerResponse<T>extracts the promised response.RequestUser<T>extracts the user from the request.
You could jam that into one long conditional type. Do not reach for that first. Name the middle when the pattern has a story, just like 10.2's rule for deep indexed access chains.
Now you can inspect types, pull pieces out, and choose branches. Section 11 turns from inspection to transformation: mapped types walk every key in a shape and build a new shape from it.
Checkpoint
Answer all three to mark this lesson complete