Skip to content
webtype.orgTwelve concepts · 8/12

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.

What "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.

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.

Practiced in