Conditional Types & infer

Difficulty: Advanced

Question

Explain conditional types and the 'infer' keyword. How do you use them to extract and transform types?

Answer

Conditional types select one of two types based on a condition, using the syntax: T extends U ? X : Y (if T is assignable to U, resolve to X, else Y).

The 'infer' keyword lets you extract a type from within a pattern. It works like a type-level variable that captures part of a type during pattern matching.

Key behaviors: - Distributive: when applied to a union, the conditional distributes over each member - infer: captures a type variable from within a conditional check - Nested: conditional types can be chained for complex logic

Built-in types like ReturnType, Parameters, and Awaited are all conditional types with infer.

Code examples

Basic Conditional Types

// Simple conditional
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false
type C = IsString<'hello'>; // true (literal extends string)

// Distributive conditional (distributes over unions)
type ToArray<T> = T extends any ? T[] : never;

type D = ToArray<string | number>;
// Distributes: ToArray<string> | ToArray<number>
// Result: string[] | number[] (NOT (string | number)[])

// Prevent distribution with [T]
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type E = ToArrayNonDist<string | number>;
// (string | number)[] - no distribution

// NonNullable implementation
type MyNonNullable<T> = T extends null | undefined ? never : T;
type F = MyNonNullable<string | null | undefined>;
// Distributes: string | never | never = string

Conditional types distribute over union members by default. Each member is checked individually, and results are unioned. Wrap in tuple [T] to prevent distribution.

infer Keyword - Type Extraction

// Extract return type of a function
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type R1 = MyReturnType<() => string>;        // string
type R2 = MyReturnType<(x: number) => boolean>; // boolean
type R3 = MyReturnType<typeof Math.random>;   // number

// Extract parameter types
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

type P1 = MyParameters<(a: string, b: number) => void>;
// [a: string, b: number]

// Extract Promise inner type
type Unwrap<T> = T extends Promise<infer U> ? U : T;

type U1 = Unwrap<Promise<string>>;    // string
type U2 = Unwrap<Promise<number[]>>;  // number[]
type U3 = Unwrap<string>;             // string (not a promise)

// Extract array element type
type ElementOf<T> = T extends (infer E)[] ? E : never;

type E1 = ElementOf<string[]>;    // string
type E2 = ElementOf<number[]>;    // number
type E3 = ElementOf<[1, 'a', true]>; // 1 | 'a' | true

infer declares a type variable that TypeScript fills in during pattern matching. It's like destructuring but for types.

Advanced Conditional Patterns

// Deep Awaited (recursive unwrap of promises)
type DeepAwaited<T> = T extends Promise<infer U>
  ? DeepAwaited<U>
  : T;

type A1 = DeepAwaited<Promise<Promise<Promise<string>>>>; // string

// Extract specific property types
type PropertyType<T, K extends string> = 
  T extends { [P in K]: infer V } ? V : never;

type NameType = PropertyType<{ name: string; age: number }, 'name'>;
// string

// Flatten function type
type FlattenFunction<T> = T extends (...args: infer A) => infer R
  ? { args: A; returnType: R }
  : never;

type Info = FlattenFunction<(name: string, age: number) => boolean>;
// { args: [name: string, age: number]; returnType: boolean }

// Conditional with multiple infer positions
type Head<T> = T extends [infer First, ...infer Rest] ? First : never;
type Tail<T> = T extends [infer First, ...infer Rest] ? Rest : never;

type H = Head<[1, 2, 3]>; // 1
type T = Tail<[1, 2, 3]>; // [2, 3]

Conditional types with infer enable powerful type-level pattern matching. You can decompose tuples, extract nested types, and create recursive type transformations.

Key points

Concepts covered

Conditional Types, infer, Distributive Conditionals, Type Extraction, ReturnType