Obscure TypeScript: Distributive Conditional Types
Conditional types become
distributive
when acting on a generic type that is given a union type. In simpler terms, if you write
a conditional type that uses a generic, and that generic is given a union (e.g.
'foo' | 'bar'
), the condition within the conditional type will apply to each member
of the union:
type OnlyStrings<T> = T extends string ? T : T;
type Strings = OnlyStrings<"foo" | "bar" | "baz" | 1 | 2 | 3>;
type PassThrough = OnlyStrings<{ foo: "bar" }>;
In this example, we can see that if we supply a union type to the generic of
OnlyStrings
, the conditional becomes distributive and acts on each member of the
union, filtering out anything that isn’t a string
. However, if the generic isn’t a
union (i.e. { foo: "bar" }
), then the conditional will apply to the whole type
instead, which results in the type being returned as-is.
So when would this be useful? You might have already honed in on this, but this is
actually how TypeScript’s
Exclude
utility type works under the hood! Knowing how conditional types can be distributive, we
can implement Exclude
ourselves:
type MyExclude<UnionType, ExcludedMembers> = UnionType extends ExcludedMembers
? never
: UnionType;
type Excluded = MyExclude<"a" | "b", "b">;