Conditional Types

Branch on type relationships with extends, infer, and distribution

Overview

Conditional types choose one of two types based on a compile-time relationship, using the form T extends U ? X : Y. Combined with the infer keyword they can extract and reshape parts of a type, and they distribute over unions automatically. They power many of the built-in utility types.

Syntax / Usage

The extends clause is a type-level assignability test, not runtime code. infer introduces a fresh type variable captured from the matched position.

// Basic conditional
type IsString<T> = T extends string ? true : false
type A = IsString<"hi">  // true
type B = IsString<number> // false

// infer extracts the element type of an array
type ElementOf<T> = T extends (infer E)[] ? E : never
type C = ElementOf<number[]> // number

// infer the resolved value of a Promise
type Awaited2<T> = T extends Promise<infer V> ? V : T
type D = Awaited2<Promise<string>> // string

Examples

Conditional types distribute over each member of a union.

type NonNullish<T> = T extends null | undefined ? never : T
type Clean = NonNullish<string | null | undefined> // string

Extract a function's return type by inferring the position after =>.

type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never

function load() {
  return { id: 1, ok: true }
}
type Loaded = ReturnOf<typeof load> // { id: number; ok: boolean }

Wrap distribution in a tuple to compare the whole union at once.

// [T] prevents distribution, so this tests the union as a single type
type IsExactlyString<T> = [T] extends [string] ? true : false
type E = IsExactlyString<string | number> // false

Common Mistakes

  • Forgetting that naked type parameters distribute over unions, changing results
  • Placing infer outside a conditional type, where it is not allowed
  • Assuming extends runs at runtime—it is purely a type-level check
  • Nesting deep conditionals that hurt readability and compile performance
  • Returning any from the false branch instead of a precise never

See Also

typescript-mapped-types generics utility-types