Conditional Types & infer
A conditional type uses the form T extends U ? X : Y to choose one type when T is assignable to U and another type otherwise — an "if/else" at the type level — and the infer keyword lets you capture and extract a sub-type from inside the type you are matching against.
Learn Conditional Types & infer in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
A conditional type is a sorting machine with a single question : "does this fit through the U-shaped hole?" If yes, it goes down chute X ; if no, chute Y . Feed it a whole bag of items (a union) and it sorts each item independently — that's distribution. The infer keyword is like a label-reader built into the machine: as an item passes the test, it peels off and remembers a sticker from inside it (the return type, the element type, the first argument) so you can use that captured piece later.
1. T extends U ? X : Y
A conditional type asks one question: is T assignable to U ? If so the type resolves to X , otherwise to Y . You can chain them like an else if ladder. This is exactly how NonNullable<T> strips null and undefined from a type:
2. Distributive Conditional Types
When the checked type is a bare type parameter and you feed it a union , the conditional applies to each member separately and re-unions the results. That is why ToArray<string | number> becomes string[] | number[] , and why filtering a union just works. Wrap both sides in [brackets] to switch distribution off and treat the union as one unit.
3. The infer Keyword
infer declares a placeholder type variable inside the extends pattern and captures whatever matches there. It is how you reach inside a function type for its return type, inside an array for its element type, or inside a Promise for its resolved value. Here is the actual definition of ReturnType alongside other infer helpers:
🎯 Your Turn
Implement the value-level form of a chained conditional type that describes a value. Fill in the blanks and match the expected output.
Build the runtime form of a chained infer conditional that unwraps a Promise or an array, and otherwise returns the value untouched. Follow the outline, run it, and match the example output.
Practice quiz
What is the syntax of a conditional type?
- T ? U : X
- if T then X else Y
- T extends U ? X : Y
- T | U ? X : Y
Answer: T extends U ? X : Y. A conditional type is T extends U ? X : Y - a type-level if/else that resolves to X when T is assignable to U, else Y.
When does a conditional type run?
- At compile time, choosing a type
- At runtime, choosing a value
- Only inside async functions
- Never - it is just documentation
Answer: At compile time, choosing a type. Conditional types resolve at compile time and choose a type, then are erased - nothing executes at runtime.
In a conditional type, what does extends actually mean?
- Class inheritance
- Has the same name as
- Is a subclass of
- Is assignable to
Answer: Is assignable to. extends here means 'is assignable to', not inheritance. So "hi" extends string is true, and 0 extends number is true.
What does ToArray<T> = T extends any ? T[] : never give for ToArray<string | number>?
A distributive conditional applies to each union member separately, so the result is string[] | number[], not a mixed array.
What triggers distribution in a conditional type?
- A bare ('naked') type parameter checked against a union
- Any conditional type
- Using infer
- Wrapping the type in brackets
Answer: A bare ('naked') type parameter checked against a union. Distribution happens when the checked type is a naked type parameter T and you pass a union; it then applies per member.
How do you DISABLE distribution and treat a union as a single unit?
- Use the never keyword
- Add the readonly modifier
Wrapping both sides in [brackets] - [T] extends [U] - stops distribution and tests the whole union at once.
What does the infer keyword do?
- Runs a value-level inference
- Declares a fresh type variable inside an extends pattern to capture a sub-type
- Converts a type to any
- Imports a type from another module
Answer: Declares a fresh type variable inside an extends pattern to capture a sub-type. infer introduces a new type variable inside a conditional's extends clause and captures whatever matches at that position.
How is the built-in ReturnType<F> defined?
- F extends infer R ? R : F
- Awaited<F>
ReturnType<F> = F extends (...args: any[]) => infer R ? R : never - 'if F is a function returning R, give me R'.
Where is infer legal?
- Anywhere a type can appear
- Only in the extends clause of a conditional type
- Only in function signatures
- Only with Promise types
Answer: Only in the extends clause of a conditional type. infer is only valid inside the extends clause of a conditional type; using it elsewhere is an error.
Why might a conditional type resolve to never unexpectedly?
- never is a syntax error
- infer always returns never
- Distribution can filter every union member out, leaving never
- extends is misspelled
Answer: Distribution can filter every union member out, leaving never. never is the natural empty result; a distributive conditional that filters out every member leaves never. Trace each member.