Conditional types use the syntax T extends U ? X : Y to branch at the type level — combined with the infer keyword to extract types from patterns and distributive behavior over unions, they power most of TypeScript's built-in utility types.
T extends U ? X : Y — if T is assignable to U, resolve to X, otherwise Y. The extends keyword is a subtype check.
Declares type variables inside extends clauses — pattern-matches against types to extract return types, element types, or any structural component.
When applied to unions, conditional types evaluate each member separately — ToArray<A | B> becomes ToArray<A> | ToArray<B>. Wrap in tuple to prevent.
Exclude, Extract, ReturnType, Parameters, Awaited, and NonNullable are all implemented as conditional types in TypeScript's standard library.
Conditional types bring if/else logic to the type system. They let you create types that depend on other types — selecting one branch or another based on a type relationship.
The syntax mirrors JavaScript's ternary operator: T extends U ? X : Y. If T is assignable to U, the type resolves to X; otherwise Y.
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // falseThe extends in conditional types means "is assignable to" — it's a subtype check, not equality.
infer Keywordinfer declares a type variable inside the extends clause that TypeScript infers from the matched pattern. It's like pattern matching for types.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ElementType<T> = T extends (infer E)[] ? E : never;
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;infer R in ReturnType tells TypeScript: "match against a function signature and bind whatever the return type is to R." If the match succeeds, return R; otherwise never.
infer can only be used in the extends clause of a conditional type. Multiple infer positions in the same clause are allowed — TypeScript infers each independently.
When a conditional type is applied to a union, it distributes — the condition is evaluated for each union member separately:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]Without distribution, the result would be (string | number)[] — a single array of mixed types. Distribution applies the transformation to each member individually.
Distribution only happens when T is a naked type parameter (directly from a generic). To prevent distribution, wrap in a tuple: [T] extends [any] ? ... : ....
TypeScript's standard library uses conditional types extensively:
Exclude<T, U> — Remove types from T assignable to U: Exclude<'a' | 'b' | 'c', 'a'> → 'b' | 'c'Extract<T, U> — Keep only types from T assignable to UNonNullable<T> — Remove null | undefined (uses Exclude internally)ReturnType<T> — Extract return type of a function typeParameters<T> — Extract parameter types as a tupleAwaited<T> — Recursively unwrap PromisesConditional types enable sophisticated type transformations:
// Extract event handler types
type EventMap = { click: MouseEvent; keydown: KeyboardEvent };
type HandlerFor<K extends keyof EventMap> = (event: EventMap[K]) => void;
// Flatten nested types
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;Recursive conditional types (supported since TypeScript 4.1) enable deep transformations like DeepPartial, DeepReadonly, and JSON type parsers.
Conditional types use T extends U ? X : Y to branch at the type level. The infer keyword extracts types from patterns (like function return types or array elements). Conditional types distribute over unions by default — each union member is evaluated separately. Most built-in utility types (Exclude, ReturnType, NonNullable) are implemented with conditional types.
Fun Fact
The `infer` keyword was added in TypeScript 2.8 (2018) and is considered one of the most powerful features in the type system. It enabled an entire ecosystem of type-level programming — libraries like ts-toolbelt and type-fest use infer extensively to implement hundreds of utility types, and some developers have even built type-level parsers and interpreters using recursive conditional types with infer.