← thecodex.expert · The Codex Family of Knowledge
TypeScript

Conditional Types

infer lets a conditional type reach INTO another type—like pulling a function’s return type back out—entirely at the type level, with zero runtime code.

TypeScript 6.0 T extends U ? X : Y Last verified:
Where this bites you

Distribution kicks in whenever the checked type is a naked type parameter — including when you didn't want it to. A helper meant to turn a type into "an array of it" quietly becomes "an array of each member" when you pass a union, so ToArray<string | number> is string[] | number[], not (string | number)[]. Then a perfectly ordinary mixed array is rejected, with an error that talks about string[].

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

type Mixed = ToArray<string | number>;   // expected (string | number)[]

const values: Mixed = ["a", 1];
// error TS2322: Type '(string | number)[]' is not assignable to type 'Mixed'.
//   Type '(string | number)[]' is not assignable to type 'string[]'.
Canonical Definition

A conditional type has the form T extends U ? X : Y: if T is assignable to U, the result is X; otherwise Y — the type-level equivalent of a ternary expression. The infer keyword, used inside the extends clause, captures part of the matched type as a new named type variable usable in the result, which is how TypeScript's own built-in utility types like ReturnType<T> extract a function's return type without ever running any code.

A basic conditional type

This is evaluated entirely at compile time, purely on types — no runtime check, no generated code for the condition itself.

TypeScriptbasic_conditional.ts
type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>;    // "yes"
type B = IsString<number>;    // "no"

infer: extracting a type from within the condition

Rather than just returning a fixed X or Y, infer lets the result reuse a piece captured FROM the matched type itself — this exact pattern is how TypeScript's own ReturnType<T> is implemented.

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

function getUser() {
    return { name: "Alice", age: 30 };
}

type UserResult = MyReturnType<typeof getUser>;
// { name: string; age: number } — extracted, without calling getUser at all

Distributive conditional types over a union

When T in T extends U ? X : Y is a "naked" type parameter and the actual argument is a union, TypeScript applies the conditional to EACH member of the union separately and unions the results — a subtle but genuinely useful behavior, since it's how filtering a union by type works.

TypeScriptdistributive.ts
type ExcludeString<T> = T extends string ? never : T;

type Result = ExcludeString<string | number | boolean>;
// distributes over each member: never | number | boolean
// simplifies to: number | boolean

Distribution only applies to a naked type parameter, which is also the switch to turn it off. Wrapping both sides of extends in a one-element tuple makes T no longer naked, so the union is checked as a whole:

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

type Mixed = ToArray<string | number>;   // (string | number)[]

const values: Mixed = ["a", 1];
console.log(values);   // [ 'a', 1 ]

Sources

1
Microsoft. TypeScript Handbook, "Conditional Types", typescriptlang.org/docs/handbook/2/conditional-types.html.