Lesson 39 of 80
infer isn't limited to one obvious spot — it can reach into a function's last parameter, a tuple's tail, or peel back layers of Promise wrapping. The pattern is always the same: shape a type that matches what you're looking for, and let infer name the piece you actually want.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The conditional-types lesson introduced infer with the simplest case — pulling an array's element type out of T extends (infer U)[] ? U : never. That's one shape. infer works anywhere inside the type you're pattern-matching against, which means the real skill isn't memorizing infer's syntax — it's writing the shape of the type you're looking for, with infer marking the one piece you want back.
type Head<T extends readonly unknown[]> = T extends readonly [infer First, ...unknown[]]
? First
: never;
type A = Head<[string, number, boolean]>; // string
Here the "shape" being matched is "a tuple whose first element is something, followed by anything else" — and infer First captures that first slot specifically. Change where infer sits in the shape, and you capture a different piece.
type LastParam<F> = F extends (...args: [...infer _Rest, infer Last]) ? Last : never;
type A = LastParam<(a: string, b: number, cb: () => void) => void>;
// () => void
[...infer _Rest, infer Last] matches a parameter tuple as "some number of leading params, then exactly one final one" — _Rest captures everything before it (unused here, but it has to be named to make the shape valid), Last captures the one you actually want. This is precisely how you'd type a promisify-style helper that needs to isolate a Node-style callback argument from the rest of a function's signature.
type Tail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer Rest]
? Rest
: never;
type B = Tail<[string, number, boolean]>; // [number, boolean]
Same trick, mirrored: match "one throwaway first element, then the rest," and infer Rest captures everything after it as its own tuple.
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<Promise<Promise<number>>>>; // number
This combines infer with the recursive-conditional-types pattern from the previous lesson: capture what's inside the Promise, then feed it right back into UnwrapPromise in case it's another Promise. TypeScript's real Awaited<T> utility type is a more defensive version of exactly this — it also has to account for "thenables" that aren't quite real Promises, but the core mechanism is this same recursive infer.
infer keyword itself — it's writing the surrounding shape correctly. infer just marks "capture whatever goes here"; the actual design work is describing the pattern (a tuple with a rest element, a function's parameter list, a wrapped generic) precisely enough that TypeScript can match it.[...infer Rest, infer Last] only works because tuple types support rest elements in this position — this is leaning on TypeScript's variadic tuple types, not something infer does independently. The two features compose, but knowing that distinction helps when something similar doesn't work the way you expect.infer capture still has to be named. In LastParam, _Rest is never referenced in the true branch, but the pattern is invalid without a name there — TypeScript needs something in that tuple slot to match against, even if you never use what it captures.infer (as in UnwrapPromise) needs the same base-case discipline as any other recursive conditional type — without the fallback to plain T when it's no longer a Promise, there's nothing to stop the recursion once it reaches a non-Promise value.Interviewers use exactly these three examples — a function's last argument, a tuple's tail, a deeply-nested Promise — because they force you past "I know what infer does in the one textbook example" into actually designing the matching shape yourself. Being able to say out loud "I need a pattern that matches a tuple with a rest element up front and one final slot, and I'll infer that last slot" is the tell that you understand the mechanism, not just the keyword.