🔷 TypeScript Q90 / 101

What is distributive conditional type?

AI-Powered Answer ✓ Answered

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.

typescript
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'.

typescript
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 from T those types that are assignable to U. (e.g., Exclude<string | number | boolean, string> results in number | boolean).
  • Extract<T, U>: Extracts from T those types that are assignable to U. (e.g., Extract<string | number | boolean, string> results in string).
  • NonNullable<T>: Excludes null and undefined from T. (e.g., NonNullable<string | null | undefined> results in string).
  • ReturnType<T> and Parameters<T>: While not directly distributing over a union of argument/return types, they rely on conditional types where the function type T is 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.