Discriminated Unions in Practice

This lesson contains some facts about discriminated unions that may come in handy when using them in the wild.

Non-string discriminators

In the example from the previous lesson, we used a string literal property called type as a discriminator. First, the discriminator doesn’t have to be called "type". Any property name will be fine.

Second, the discriminator doesn’t have to be a string. You can also use number literals, Boolean literals, or even enums!

The built-in IteratorResult type in TypeScript (starting from version 3.6) uses the Boolean property done as a discriminator.

type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;

interface IteratorYieldResult<TYield> {
    done?: false;
    value: TYield;
}

interface IteratorReturnResult<TReturn> {
    done: true;
    value: TReturn;
}

Using enums can help you avoid magic strings if that’s an issue for you. One advantage of this approach is that renaming discriminating values becomes much easier.

const enum ContactType { Phone, Email };

type Contact =
    | { type: ContactType.Email, email: string }
    | { type: ContactType.Phone, phone: number };

Multiple discriminating properties

Interestingly, a discriminator does not have to be a single property. A group of literal properties can also act as a discriminator! In such a case, every combination of values marks a different member of the union type.

Get hands-on with 1400+ tech skills courses.