Concept 8 of 12
Distributivity
Distribution is the rule that surprises everyone once and then explains half the confusing behaviour in the type system. A conditional whose checked side is a bare type parameter does not run once — it runs once per union member, and the results are unioned back together.
- Expression
Each<string> - Resolves to
string[] - Expression
Each<number> - Resolves to
never - Expression
Each<string | number> - Resolves to
string[] - Expression
Whole<string | number> - Resolves to
never
Each checks union members separately, then combines the results; never drops out. Whole checks the entire union at once.
Type definition
type Each<T> = T extends string ? T[] : never
type Whole<T> = [T] extends [string] ? T[] : neverWhat "naked" means
type Naked<T> = T extends string ? 'yes' : 'no' type A = Naked<string | number> // 'yes' | 'no' — ran twice type Wrapped<T> = [T] extends [string] ? 'yes' : 'no' type B = Wrapped<string | number> // 'no' — ran once, on the whole union
Naked means the parameter stands alone on the left of extends, not wrapped in a tuple, array, object or promise. Wrapping it in anything switches distribution off and compares the union as a single thing.
Distribution is how filtering works
type MyExclude<T, U> = T extends U ? never : T MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c' // Each member becomes never or survives; unioning drops the nevers, // because never is the identity element of union.
Exclude, Extract and NonNullable are all one distributive conditional. The trick is that never vanishes from a union, so mapping unwanted members to never removes them for free.
Two idioms worth memorising
// Force distribution, even when you do not need the test: type Each<K> = K extends K ? [K] : never // Suppress it, and detect the empty union: type IsNever<T> = [T] extends [never] ? true : false
K extends K is trivially true and exists purely to distribute. [T] extends [never] is the only reliable way to ask "is this never", because a distributive conditional over never produces never and never reaches either branch.
The common wrong answer
// Intent: does T contain string? type HasString<T> = T extends string ? true : false HasString<string | number> // boolean, not true // It distributed: true | false, which collapses to boolean. // Fixed: ask about the union as a whole. type HasString2<T> = [T] extends [string] ? true : false
A conditional returning boolean instead of true or false is nearly always distribution you did not intend. The union of true and false is boolean, which is why the symptom looks so innocuous.
Exercise
Remove null from a union — and let the conditional distribute.
Try it
2NoNull<string | null>—→ stringNoNull<null>—→ never
Takeaway
A bare parameter distributes; wrapping it in [] stops it. When a type mysteriously returns boolean or never, distribution is the first thing to check.

