Topic 9.3
The infer Keyword
In one line
Inside the extends part of a conditional type, infer X says "whatever type sits here, call it X", so you can pull out element types, return types and promise values.
Think of it like this
A claim ticket at a coat check. You hand over a coat and get a ticket with a number; later the ticket lets you get back exactly what was inside. **infer is a claim ticket for part of a type.** You describe the shape ("an array of something"), put a ticket on the "something" (infer E), and if the shape matches, the ticket gives you that part back to use in the answer.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- infer
- A keyword that names a piece of a type while the compiler matches a pattern, so you can use that piece in the answer.
- Pattern
- The shape written after
extends, likePromise<infer V>or(infer E)[]. - Fallback
- The answer in the "no" branch, used when the shape does not match.
- Return type
- The type of the value a function gives back.
- Tuple
- A fixed-length array type where each position has its own type, like
[string, number]. - Covariant position
- A spot where a more specific type is allowed, like a return type. Several candidates make a union.
- Contravariant position
- A spot like a function parameter, where the direction flips. Several candidates make an intersection.
- Constraint (on infer)
infer X extends T: the inferred piece must also fitT, otherwise the pattern does not match.
Step by step
01Ticket an array's items
Match the shape "array of something" and name the something E. The answer is E.
For string[], E is string. For a type that isn't an array, the pattern fails and the fallback never is used.
type ElementOf<T> = T extends (infer E)[] ? E : never;
type A = ElementOf<string[]>; // string
type B = ElementOf<(number | boolean)[]>; // number | boolean
type C = ElementOf<string>; // never02Unwrap a promise
Promise<infer V> pulls the value out of a promise type. Here the fallback is T itself, so a plain boolean passes through.
Notice Unwrap<Promise<Promise<number>>> gives Promise<number>: it peels one layer. Peeling all layers needs recursion (Topic 9.5), and it's what the built-in Awaited does.
type Unwrap<T> = T extends Promise<infer V> ? V : T;
type P1 = Unwrap<Promise<string>>; // string
type P3 = Unwrap<boolean>; // boolean03Rebuild ReturnType and Parameters
For a function, ticket the return type with infer R and the whole parameter list with infer P. Recall from Topic 8.2: typeof makeUser is the type of the function value. P is a tuple (Topic 5.2: a fixed-length array type), and its items keep their names: [name: string, age: number].
never[] and unknown in the pattern make the matching as wide as possible, so any function fits. Why never[] and not unknown[]? A function parameter is a contravariant spot (Topic 7.7): a function that needs a string is only safe where callers will pass strings. With unknown[] callers might pass anything, so makeUser would be rejected and the answer would be never. With never[] no caller can pass any value at all, so every function qualifies.
type MyReturn<F> = F extends (...args: never[]) => infer R ? R : never;
type MyParams<F> = F extends (...args: infer P) => unknown ? P : never;
function makeUser(name: string, age: number) {
return { name, age, active: true };
}
type User = MyReturn<typeof makeUser>; // { name: string; age: number; active: boolean }
type Args = MyParams<typeof makeUser>; // [name: string, age: number]04Pick tuple positions
[infer H, ...unknown[]] means "a first item, then anything". [...unknown[], infer L] means "anything, then a last item".
Add extends number to the infer and the match works only when that item fits: FirstNumber<["x", 42]> takes the fallback.
05Two tickets with the same name
In return positions, two candidates make a union. In parameter positions, they make an intersection, and string & number collapses to never. This is the root of a famous trick that turns a union into an intersection.
Try it yourself
- 1
Get the second parameter
Write
SecondParam<F>that gives the type of a function's second parameter. Try it onmakeUser. Expectnumber.Hint: infer the parameter list first, then match
[unknown, infer S, ...unknown[]]. - 2
Peel promises
Make
Unwraprecursive: in the yes branch, returnUnwrap<V>. CheckUnwrap<Promise<Promise<number>>>now givesnumber. - 3
Change the fallback
In
ElementOf, changenevertoT. PredictElementOf<string>, then check. This is the difference between "fail loudly" and "pass through".
Code & diagrams
Each infer names one piece. Check that the fallback appears when the shape does not match (C and NotFn are never).
Console
(nothing was printed)infer H extends number keeps the exact literal 42. The text "5" becomes the number type 5, and "five" does not parse as a number, so it takes the fallback.
Console
(nothing was printed)Return positions combine into a union (string | number). Parameter positions combine into an intersection, and string & number is never. LastArg first tickets the whole parameter list, then its last item.
Console
(nothing was printed)Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A pattern that is too narrow
In the first example, change the pattern of MyReturn from (...args: never[]) => infer R to (...args: string[]) => infer R. The call MyReturn<typeof makeUser> passes a function that takes a string and a number.
Myth vs fact
Myth
infer declares a new type variable anywhere in a type.
Fact
Only inside the extends clause of a conditional type, and the name is only visible in the true branch.
Myth
infer always gives back the exact type.
Fact
It gives the best match for the pattern. With several candidates it makes a union (covariant) or an intersection (contravariant), which may not be what you expected.
Myth
If infer fails I get an error.
Fact
No. A failed match silently takes the false branch, so check the fallback type in the Types panel.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
infer X extends T(4.7) removed the nested conditional type that was needed to constrain an inferred piece. It also affects what is inferred:infer N extends numberin a template literal makes the compiler try to parse the text as a number, and a text that does not survive the round trip, like"05", gives the plain typenumberinstead of a literal. - ▸
The union-to-intersection trick used in library code relies on the contravariant rule: put each member in a function parameter, then
inferthe parameter once. The members are intersected. It works, but it is the kind of cleverness Topic 10.6 warns about. - ▸
Overloaded functions:
inferon a function with several overloads sees only the last overload.ReturnTypeof an overloaded function therefore gives the return type of the final signature.
Remember this
- 1
The problem this solves: a conditional type (Topic 9.1) can ask "is this an array?", but not "an array of what?". Yet you often need that inner piece: the item type of an array, the value inside a
Promise, the type a function returns.inferlets you name that piece so the answer can use it. Without it, you would have to write a separate type for every case by hand. - 2
infercan only be written in theextendspart of a conditional type.T extends (infer E)[] ? E : neverreads: "ifTis an array of something, call that somethingEand answerE; otherwisenever". The nameEexists only in the "yes" branch. Writinginferanywhere else is an error (TS1338). - 3
The pattern matching works by shape.
ElementOf<string[]>matches(infer E)[]withE = string.ElementOf<string>does not match (a string is not an array), so you get the fallback,never. Always decide the fallback:neverfor "this should not happen", orTitself when you want to pass other types through unchanged, likeUnwrap<boolean>below. - 4
This is exactly how
ReturnType<F>andParameters<F>are written.F extends (...args: never[]) => infer R ? R : neverasks: "isFa function? If so, call its return typeR." You can putinferin the parameters ((...args: infer P)), in a promise (Promise<infer V>), in tuple positions ([infer H, ...unknown[]],[...unknown[], infer L]) and in template strings (Topic 9.4). - 5
If the same name is inferred in two places, TypeScript combines the candidates. In a covariant spot (a return type, a property) the result is a union:
string | number. In a contravariant spot (a function parameter) the result is an intersection:string & number, which isnever(Topic 7.7 explained the two directions). It's a small surprise worth knowing before you see it in a library. - 6
**
infer X extends T** (TypeScript 4.7) adds a constraint: infer a piece only if it also fitsT.[infer H extends number, ...unknown[]]matches only tuples whose first item is a number, andHkeeps the exact literal (42). With template strings, `${infer N extends number}turns the text"5"into the number type5`. Before 4.7 you needed a second nested conditional type. - 7
Matching is strict about what is expected. If you ask for a function that takes strings (
(...args: string[]) => infer R) and give it one taking a string and a number, the shapes don't match and you get the fallback. Check the Types panel when aninferreturnsneverunexpectedly: the pattern is usually a little too narrow.
Explain it without notes
What does infer do, and where may it be written?
How is ReturnType<F> written, step by step?
What happens when the same name is inferred in two positions?
What does infer X extends T add, and since which version?
Practice
Write Last<T> giving the last item of a tuple type and test it on [1, 2, 3].
Write PromiseValue<F> for an async function type: the value its promise resolves to. Test it on async () => 42.
Write Tail<T extends unknown[]> that returns the tuple without its first item.
Trade-offs
- ↔
infermakes small, reusable extractors, but each one is a puzzle for a reader. Prefer the built-ins (ReturnType,Parameters,Awaited) when they exist.
Done when you can
I can say where
infermay be used and where its name is visible.I can write extractors for array items, promise values and return types.
I can pick tuple positions with
infer.I can explain union vs intersection for repeated
infernames.I can use
infer X extends Tand name the version it arrived in.