typestar

Distributive conditionals in TypeScript

A conditional over a naked type parameter runs once per union member.

type Boxed<T> = T extends unknown ? { value: T } : never;
type NotBoxed<T> = [T] extends [unknown] ? { value: T } : never;

type Distributed = Boxed<string | number>;
type Together = NotBoxed<string | number>;

type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type MyNonNullable<T> = T extends null | undefined ? never : T;

type Levels = "debug" | "info" | "warn" | "error";
type Loud = MyExclude<Levels, "debug" | "info">;
type Quiet = MyExtract<Levels, "debug" | "info">;

const one: Distributed = { value: 1 };
const both: Together = { value: "either" };
const loud: Loud = "error";
const quiet: Quiet = "debug";
const defined: MyNonNullable<string | null> = "here";
console.log(one, both, loud, quiet, defined);

How it works

  1. Distribution is why Exclude and Extract work.
  2. Wrapping the parameter in a tuple switches distribution off.
  3. It is the single most surprising rule in the type system.

Keywords and builtins used here

The run, in numbers

Lines
20
Characters to type
763
Tokens
206
Three-star pace
105 tpm

At the three-star pace of 105 tokens a minute, this run takes about 118 seconds.

Type this snippet

Step 3 of 3 in Conditional types, step 3 of 12 in Type-level programming.

← Previous Next →