๐ŸŽ“ A1Learner
๐Ÿ”
0 of 37 lessons completed

TypeScript Distributive Conditionals

A distributive conditional runs its check once per member when the checked type is a naked type parameter holding a union. The separate results join back into one union.

A naked parameter distributes

ToArray checks its naked parameter against unknown. The union input runs the check twice, once for string and once for number.

type ToArray<T> = T extends unknown ? T[] : never;
type Mixed = ToArray<string | number>;
const texts: Mixed = ['a', 'b'];
const counts: Mixed = [1, 2];
console.log(texts.join(','));
console.log(counts.join(','));

A tuple wrapper stops distribution

The tuple wrapper makes the check see the whole union at once. The result is one array type that mixes both members.

type ToArrayWhole<T> = [T] extends [unknown] ? T[] : never;
type Whole = ToArrayWhole<string | number>;
const both: Whole = ['a', 1, 'b'];
console.log(both.join(','));

MyExclude rebuilt with distribution

The intermediate course used the built-in Exclude, and MyExclude rebuilds it from a conditional. Distribution drops the admin member by member and keeps the rest.

type MyExclude<T, U> = T extends U ? never : T;
type Route = 'home' | 'admin' | 'settings';
type PublicRoute = MyExclude<Route, 'admin'>;
const page: PublicRoute = 'home';
const other: PublicRoute = 'settings';
console.log(page);
console.log(other);

Example

type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type Events = 'click' | 'scroll' | 'hover';
type Pointer = MyExtract<Events, 'click' | 'hover'>;
type Rest = MyExclude<Events, 'click' | 'hover'>;
const p: Pointer = 'click';
const r: Rest = 'scroll';
console.log(p);
console.log(r);

Key points

  • A type parameter is naked when the check uses the parameter alone, with no wrapper around it.
  • Distribution runs the conditional once for each union member and unions the branch results.
  • Wrapping the parameter in a one-member tuple, as [T] extends [U], switches distribution off.
  • The boolean type is a union of true and false, so a naked check over boolean runs twice and unions both branches.
  • Distribution is the machinery that makes union filters work member by member.

In this example, MyExtract and MyExclude both distribute over the event union, so each filter judges every member on its own.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

How does a conditional type stop distribution over a union?