Skip to content
Conditional Types and infer
step 1/7

Reading — step 1 of 7

Learn

~3 min readThe Type System Toolkit

Conditional types let the type system make decisions. They look like ternaries but operate at the type level — and unlock most of TypeScript's type-system magic. The TypeScript Handbook treats them as the heart of advanced type-level programming.

Basic syntax

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

type A = IsString<'hi'>;     // true
type B = IsString<42>;        // false
type C = IsString<string>;    // true

T extends U ? X : Y reads: "if T is assignable to U, the type is X; otherwise Y."

Useful for branching:

type Stringable<T> = T extends number | boolean | string ? string : never;

Distribution over unions

The magic property: when T in T extends X ? ... is a naked union, the conditional distributes over each member:

type ToArray<T> = T extends any ? T[] : never;

type A = ToArray<string | number>;
// = (string extends any ? string[] : never) | (number extends any ? number[] : never)
// = string[] | number[]

Distribution lets you transform every member of a union independently. Disable distribution by wrapping in [T]:

type ToArray2<T> = [T] extends [any] ? T[] : never;
type B = ToArray2<string | number>;     // (string | number)[]

Built-in conditional utilities

TypeScript ships several:

  • Exclude<T, U> — removes types from T assignable to U:
    type WithoutString = Exclude<string | number | boolean, string>;
    // number | boolean
    
  • Extract<T, U> — keeps only types from T assignable to U:
    type OnlyString = Extract<string | number | boolean, string>;
    // string
    
  • NonNullable<T> — removes null and undefined:
    type Defined = NonNullable<string | null | undefined>;     // string
    

infer — capture types within a conditional

The infer keyword introduces a type variable inside an extends clause:

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

type A = ReturnType<() => string>;                   // string
type B = ReturnType<(x: number) => boolean>;          // boolean
type C = ReturnType<typeof Math.max>;                 // number

The infer R says "if T is a function type, capture its return type as R."

Common infer patterns:

// Get the parameters of a function:
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

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

// Unwrap a Promise:
type Awaited<T> = T extends Promise<infer U> ? U : T;
type X = Awaited<Promise<string>>;       // string

// First and rest of a tuple:
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type Tail<T extends any[]>  = T extends [any, ...infer R] ? R : [];

These are the building blocks of every TypeScript utility library.

Real-world example — typed config

type HttpHandler<T> = T extends `GET ${string}`
    ? { get: () => Response }
    : T extends `POST ${string}`
        ? { post: (body: any) => Response }
        : never;

type GetHandler  = HttpHandler<'GET /users'>;     // { get: ... }
type PostHandler = HttpHandler<'POST /users'>;     // { post: ... }
type Bad         = HttpHandler<'PATCH /users'>;    // never — caught at compile time

Route strings drive the shape of the handler — invalid routes become never, surfacing the bug.

Common mistakes

  • Forgetting distributionExclude<string | number, string> works because conditionals distribute. Wrapping in [T] disables it (sometimes desired).
  • Trying to infer outside an extends clauseinfer only appears inside the type after extends in a conditional.
  • Deep recursion in conditional types — TypeScript caps recursion. Use tail-call-style patterns (TS 4.7+) or iteration via tuple length.
  • Mixing union and neverT extends X ? Y : never is the common pattern to FILTER unions; produces never for non-matches, which then disappear from the result.
  • Conditional types in regular code paths — they're powerful but make signatures hard to read. Use sparingly in public APIs.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…