stackademic

The leading education platform for anyone with an interest in software development.

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