Discriminated Unions or Tagged Unions Types
Tags: typescript, beginners, programming, tutorial
Published: 2022-06-10 21:14:42

TypeScript Narrowing #4

{% embed https://www.youtube.com/embed/o0v7lJF11Pg %}
_See this and many other articles at [lucaspaganini.com](lucaspaganini.com)_

Heeeello and welcome to the fourth article in our TypeScript narrowing series. We're continuing from where we left off, so If you've missed any of the previous articles, I suggest you go back and read them.

In this article, we will cover tagged union types (AKA discriminated unions).

And we have more to come, in future articles:

1. Assertion guards
2. Higher-order guards
3. Narrowing library

<p>I'm Lucas Paganini, and on this site, we release web development tutorials. <app-newsletter-modal-trigger>Subscribe to the newsletter</app-newsletter-modal-trigger> if you're interested in that.</p>

## Tagged Union Types (Discriminated Unions)

So, tagged union types... what are they?

It's pretty self-explanatory. They are union types with a tag 😅

For example, you could have a union type called `Log` that aggregates three interfaces: `Warning`, `Debug`, and `Information`.

```ts
type Log = Warning | Debug | Information;

interface Warning {
  text: string;
}

interface Debug {
  message: string;
}

interface Information {
  msg: string;
}
```

Cool, now we have a union type. To turn that into a _tagged_ union type, we need a common property with literal types between our interfaces. It could be any property name, but to simulate a real-world example, we'll call it `.subscribeToTheNewsletter`.

This property will serve as an ID for the different types of interfaces that make up the `Log` type. Every interface will have a different literal type for that property.

```ts
type Log = Warning | Debug | Information;

interface Warning {
  subscribeToTheNewsletter: 'like';
  text: string;
}

interface Debug {
  subscribeToTheNewsletter: 'comment';
  message: string;
}

interface Information {
  subscribeToTheNewsletter: 'share';
  msg: string;
}
```

And that's it. The `.subscribeToTheNewsletter` property is serving as a _tag_ for the `Log` type, so it is now a tagged union type.

## Discriminated Union Terminology

As I've mentioned before, this is also known as a discriminated union, and our `.subscribeToTheNewsletter` property is the discriminant property of `Log`.

To quote from the TypeScript docs: "When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union."

From now on, I'll use the "discriminated unions" terminology, instead of "tagged union types" because that's how TypeScript is calling it nowadays, and I want to be consistent with the official terminologies – even though I believe "tagged union types" would be a better name for it.

## Type Guards for Discriminated Unions

_"Ok Lucas, I got it. But what's the point? What do I get to do with a discriminated union?"_

Good question.

What you get with a discriminated union is a type guard for all the possible discriminations of the union type.

For example, if you have a variable called `value` that is a `Log`. That means that `value` could be either a `Warning`, a `Debug` or an `Information`.

```ts
let value: Log;

if (isWarning(value)) {
  // Handle the Warning case
} else if (isDebug(value)) {
  // Handle the Debug case
} else if (isInformation(value)) {
  // Handle the Information case
}

const isWarning =
  (value: unknown): value is Warning => { ... }

const isDebug =
  (value: unknown): value is Debug => { ... }

const isInformation =
  (value: unknown): value is Information => { ... }
```

But instead of creating individual guards for those three interfaces, you could just check the value of the discriminant property.

If `.subscribeToTheNewsletter` is `"like"`, you know that it's a `Warning`. If it's `"comment"`, it's a `Debug`. And if it's `"share"`, it's an `Information`.

```ts
let value: Log;

if (value.subscribeToTheNewsletter === 'like') {
  // Handle the Warning case
} else if (value.subscribeToTheNewsletter === 'comment') {
  // Handle the Debug case
} else if (value.subscribeToTheNewsletter === 'share') {
  // Handle the Information case
}
```

And you can make your code even clearer by going an extra mile and _switching_ your if statements – got it? 😉 "switching"... "switch"... no? ok...

```ts
let value: Log;

switch (value.subscribeToTheNewsletter) {
  case 'like':
  // Handle the Warning case
  case 'comment':
  // Handle the Debug case
  case 'share':
  // Handle the Information case
}
```

## Conclusion

References and links for the previous articles are in the references.

If you enjoyed the content, you know what to do.

And if your company is looking for remote web developers, consider contacting me and my team. You can do so on [lucaspaganini.com](https://lucaspaganini.com).

Have a great day, and I'll see you soon.

## Related content

1. [TypeScript Narrowing Part 1 - What is a Type Guard](https://youtu.be/EoPubc1u5IY)
2. [TypeScript Narrowing Part 2 - Fundamental Type Guards](https://www.youtube.com/watch?v=ZdMfiVK7fcM&ab_channel=LucasPaganini)
3. [TypeScript Narrowing Part 3 - Custom Type Guards](https://www.youtube.com/watch?v=FvSaID3TjhI&ab_channel=LucasPaganini)

## References

1. [TypeScript docs on narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html)
2. [TypeScript docs on discriminated unions](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)