100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Conditional Types in TypeScript

Branch at the type level with `T extends U ? X : Y`, including how distributive conditional types behave over union types.

Advanced TypesAdvanced15 min readJul 8, 2026
Analogies

1. Introduction

Conditional types let you express type-level if/else logic: given a type T, check whether it is assignable to U, and select one of two resulting types accordingly. This single mechanism powers much of TypeScript's advanced type inference, including built-in utilities like Exclude, Extract, ReturnType, and Awaited. Conditional types become especially powerful when combined with generics, because the branch chosen can depend on a type parameter supplied by the caller of a generic function or type.

🏏

Cricket analogy: A selector's rule 'if a player extends Allrounder, pick BattingSlot, else pick BowlingSlot' is type-level if/else logic — the same mechanism behind built-in selection rules like picking specialists or excluding injured players from a squad, most powerful when the rule depends on which player a captain supplies.

2. Syntax

typescript
type ConditionalType<T> = T extends U ? TrueBranch : FalseBranch;

// A concrete example
type IsString<T> = T extends string ? "yes" : "no";

// Conditional type with inferred type using `infer`
type ElementType<T> = T extends (infer U)[] ? U : T;

// Distributive over a union (naked type parameter)
type ToArray<T> = T extends unknown ? T[] : never;

// Non-distributive: wrap T in a tuple to opt out of distribution
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;

3. Explanation

A conditional type T extends U ? X : Y evaluates the extends-check structurally: if every value assignable to T is also assignable to U, the type resolves to X, otherwise Y. When T is a 'naked' type parameter (used bare, not wrapped in an array, tuple, or another generic) and the type actually supplied at the call site is a union, TypeScript applies the conditional type to each union member separately and unions the results back together — this is called a distributive conditional type.

🏏

Cricket analogy: Given a naked selection rule applied to Batter | Bowler, TypeScript doesn't check the combined pair at once — it distributes, evaluating the rule separately for Batter and for Bowler, then unions the two separate selection outcomes back together.

For example, ToArray<string | number> does not evaluate (string | number) extends unknown ? (string|number)[] : never as one step; instead it distributes into ToArray<string> | ToArray<number>, giving string[] | number[]. To suppress distribution and treat the union as a single whole, wrap both the checked type and the extends target in a tuple ([T] extends [U]), which is exactly how ToArrayNonDist<string | number> becomes (string | number)[] instead.

🏏

Cricket analogy: ToKit<Batter | Bowler> doesn't evaluate the pair as one combined kit request — it distributes into ToKit<Batter> | ToKit<Bowler>, giving separate batting and bowling kits; wrapping both sides in brackets like [Player] extends [Batter|Bowler] forces one single combined-kit evaluation instead.

The infer keyword can only appear inside the extends clause of a conditional type. It introduces a new type variable that TypeScript solves for by structural pattern matching, which is how ReturnType<T>, Parameters<T>, and Awaited<T> extract nested types without you writing the matching logic by hand.

Gotcha: distribution only happens for a bare, unmodified type parameter. If you write T extends any ? T[] : never and call it with a union, you get the distributed (union of arrays) result — but if you accidentally wrap T (e.g. compare [T] instead of T) you silently switch to non-distributive behavior, which is a common source of confusing type mismatches when refactoring generic type-level helpers.

4. Example

typescript
type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"

function describe<T>(
  value: T
): T extends string ? "string" : T extends number ? "number" : "other" {
  return (
    typeof value === "string"
      ? "string"
      : typeof value === "number"
      ? "number"
      : "other"
  ) as any;
}

console.log(describe("hi"));
console.log(describe(42));
console.log(describe(true));

5. Output

text
string
number
other

6. Key Takeaways

  • Conditional types follow the form T extends U ? X : Y, evaluated structurally at the type level.
  • Distributive conditional types auto-distribute over each member of a union when T is a naked type parameter.
  • Wrapping T and U in tuples ([T] extends [U]) disables distribution and treats a union as one whole type.
  • infer inside the extends clause captures and names a sub-type for use in the true branch.
  • Built-ins like Exclude, Extract, ReturnType, and Awaited are all conditional types under the hood.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#TypeScriptProgrammingStudyNotes#Programming#ConditionalTypesInTypeScript#Conditional#Types#Syntax#Explanation#StudyNotes#SkillVeris