What is distributive behavior of union types?
What distributive behavior of union types is
Distributive behavior means
that when TypeScript applies a conditional type (extends ? :) to a union type,
it automatically applies the expression to each member of the union
and then combines the results back into a union.
Example 1. A simple illustration
type T = string | number;
type Box<T> = T extends any ? { value: T } : never;What happens:
- TypeScript takes
string - then
number - applies the condition to each one
- and combines the results back
type Result = Box<string | number>;
// Expands as:
// Box<string> | Box<number>
// -> { value: string } | { value: number }In short: Box<string | number> ≡ { value: string } | { value: number }.
Why this happens
Because in conditional types, when the left side of extends is a type parameter (T),
rather than a concrete type like {} or unknown,
TypeScript unfolds the union and applies the condition separately to each member.
This is a special rule - "distributive conditional types".
Example 2. Let's check non-distributive behavior
If you wrap T in a "container", the distributive behavior disappears:
type Boxed<T> = [T] extends [any] ? { value: T } : never;
type Result = Boxed<string | number>;
// Now it does not distribute!
// [string | number] does not extend [any] distributively
// Result: { value: string | number }The difference:
| Expression | Result |
|---|---|
T extends any ? X<T> : never | X<string> | X<number> |
[T] extends [any] ? X<T> : never | X<string | number> |
The [] wrapper turns off distributive behavior.
Example 3. A real case - extracting a subtype
type ExtractString<T> = T extends string ? T : never;
type R = ExtractString<string | number | boolean>;
// distributes as:
// string -> string
// number -> never
// boolean -> never
// combined: string | never | never -> stringIn short:
ExtractString<string | number | boolean> -> string.
This is how the built-in TypeScript utilities
Extract<>,Exclude<>,NonNullable<>, and others work.
Example 4. Usage in Exclude and Extract
type Exclude<T, U> = T extends U ? never : T;
type R = Exclude<"a" | "b" | "c", "a" | "c">;
// distributes as:
// "a" extends "a"|"c" -> never
// "b" extends "a"|"c" -> "b"
// "c" extends "a"|"c" -> never
// => "b"That's why
Exclude<"a" | "b" | "c", "a" | "c">gives"b".
Example 5. Comparison with a "non-distributive" version
type NonDistributiveExclude<T, U> =
[T] extends [U] ? never : T;
type R = NonDistributiveExclude<"a" | "b" | "c", "a" | "c">;
// [T] does not distribute
// => ["a" | "b" | "c"] extends ["a" | "c"] -> false
// => result = "a" | "b" | "c"By wrapping T in a tuple [],
we turn off distributive behavior and force TypeScript to treat the union as a single whole.
Example 6. An even clearer visualization
Imagine a union as a set:
T = {A, B, C}
T extends X ? Y : ZTypeScript expands this as:
(A extends X ? Y : Z)
| (B extends X ? Y : Z)
| (C extends X ? Y : Z)Key conclusions
| Feature | Behavior |
|---|---|
Works only in conditional types (extends ? :) | Yes |
Triggered when T is a naked type parameter | Yes |
Can be turned off by wrapping T in [] | Yes |
The basis of Exclude, Extract, NonNullable | Yes |
| Applied element-wise to each member of the union | Yes |
A simple summary
Distributive behavior of union types is when TypeScript applies a conditional type to each member of the union separately, and then combines the results back into a union.
You can turn this off by wrapping the type in a tuple:
[T] extends [U] ? ... : ....
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.