Skip to main content

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

javascript
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
javascript
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:

javascript
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:

ExpressionResult
T extends any ? X<T> : neverX<string> | X<number>
[T] extends [any] ? X<T> : neverX<string | number>

The [] wrapper turns off distributive behavior.


Example 3. A real case - extracting a subtype

javascript
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 -> string

In 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

javascript
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

javascript
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:

javascript
T = {A, B, C} T extends X ? Y : Z

TypeScript expands this as:

javascript
(A extends X ? Y : Z) | (B extends X ? Y : Z) | (C extends X ? Y : Z)

Key conclusions

FeatureBehavior
Works only in conditional types (extends ? :)Yes
Triggered when T is a naked type parameterYes
Can be turned off by wrapping T in []Yes
The basis of Exclude, Extract, NonNullableYes
Applied element-wise to each member of the unionYes

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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.