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.