What is distributive conditional type?
Distributive conditional types are a powerful feature in TypeScript that allow for type transformations to be applied to each member of a union type individually, rather than to the union as a whole. This behavior is automatic when the type variable being checked in a conditional type is a 'naked type parameter' and happens to resolve to a union type.
What is a Distributive Conditional Type?
A conditional type in TypeScript takes the form T extends U ? X : Y. When the type T in such a conditional type is a 'naked type parameter' (meaning it's not wrapped in another type like an array or tuple), and T is instantiated with a union type (e.g., string | number | boolean), TypeScript automatically distributes the conditional type over the members of that union.
This means that instead of evaluating (A | B) extends U ? X : Y, the compiler evaluates (A extends U ? X : Y) | (B extends U ? X : Y). Each member of the union is processed independently, and the results are then combined into a new union type.
How it Works (Example)
Consider a simple conditional type that extracts non-string members from a type.
type NonString<T> = T extends string ? never : T;
type MyUnion = NonString<string | number | boolean>;
// MyUnion resolves to (string extends string ? never : string) |
// (number extends string ? never : number) |
// (boolean extends string ? never : boolean)
// Which simplifies to never | number | boolean
// And finally to number | boolean
In this example, NonString<T> is a distributive conditional type because T is a naked type parameter. When MyUnion is defined with string | number | boolean, the NonString type is applied to each member individually: NonString<string>, NonString<number>, and NonString<boolean>. The results are then unioned together.
Preventing Distribution (Non-Distributive Conditional Types)
You can prevent a conditional type from distributing over a union type by wrapping the type parameter T in square brackets (e.g., [T] extends [U] ? X : Y). This makes T no longer a 'naked type parameter'.
type NotDistributive<T> = [T] extends [string | number] ? T : never;
type Result1 = NotDistributive<string | boolean>;
// Result1 resolves to ([string | boolean] extends [string | number] ? string | boolean : never)
// Which simplifies to never
type Result2 = NotDistributive<string | number>;
// Result2 resolves to ([string | number] extends [string | number] ? string | number : never)
// Which simplifies to string | number
In Result1, the entire union string | boolean is checked against string | number. Since boolean is not assignable to string | number, the condition fails, resulting in never. In Result2, the union string | number *is* assignable to string | number, so the condition passes, yielding string | number.
Common Use Cases
Many of TypeScript's built-in utility types leverage distributive conditional types:
Exclude<T, U>: Excludes fromTthose types that are assignable toU. (e.g.,Exclude<string | number | boolean, string>results innumber | boolean).
Extract<T, U>: Extracts fromTthose types that are assignable toU. (e.g.,Extract<string | number | boolean, string>results instring).
NonNullable<T>: ExcludesnullandundefinedfromT. (e.g.,NonNullable<string | null | undefined>results instring).
ReturnType<T>andParameters<T>: While not directly distributing over a union of argument/return types, they rely on conditional types where the function typeTis a naked type parameter, allowing them to extract specific parts of function types.
Distributive conditional types are a fundamental concept for writing robust and flexible type-level logic, especially when dealing with complex union types in TypeScript.