Conditional types

T extends U ? A : B

A conditional type picks one of two types by testing another type. Read T extends U ? A : B as if T fits in U then A else B.

IsString answers yes for string and no for number. The test runs on types only and vanishes from the compiled code.

type IsString<T> = T extends string ? "yes" : "no"; type A = IsString<string>; // "yes" type B = IsString<number>; // "no"

Distributivity

When the tested type is a naked parameter like T, the condition distributes over unions. Each member is tested alone and the answers are joined back with a union.

ToArray sees string and number separately, so C becomes a string array or a number array. Wrap T in a tuple to stop this when you need the whole union at once.

type ToArray<T> = T extends unknown ? T[] : never; type C = ToArray<string | number>; // string[] | number[]

Filter with never

never is the type with no values, so union members mapped to never simply disappear. That makes never a handy filter.

NoStrings drops string and keeps the rest, leaving number or boolean. Exclude in the next section is built on exactly this trick.

type NoStrings<T> = T extends string ? never : T; type D = NoStrings<string | number | boolean>; // number | boolean

Read API types

Real code reads API unions with Exclude and Extract instead of hand-written conditions. Exclude drops the listed members while Extract keeps only the listed ones.

NotOk holds every status except ok, and OkOrLoading holds the two retryable ones. Both helpers are conditional types under the hood.

type Status = "ok" | "error" | "loading"; type NotOk = Exclude<Status, "ok">; // "error" | "loading" type OkOrLoading = Extract<Status, "ok" | "loading">; // "ok" | "loading"

Next: infer

Article author: Arthur Isaev

Banner image