Improve your TypeScript with union types!
Tags: typescript, programming
Published: 2022-10-11 12:13:04

TypeScript's unions are a powerful feature. Let's dive into what they are and how you can use them to your advantage!

## What is union type?

Union type is a set of types that are mutually exclusive. The name _union_ (or _sum type_) comes from type theory. According to Wikipedia definition:

> The [sum type](https://en.wikipedia.org/wiki/Sum_type) is a “tagged union”. That is, for types “A” and “B”, the type “A + B” holds either a term of type “A” or a term of type “B” and it knows which one it holds.

And in TypeScript it's similar to type theory ([as programming has a lot in common with set, type, and category theory](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/)). Let's look how official documentation defines union type:

> A union type is a type formed from two or more other types, representing values that may be *any one* of those types. We refer to each of these types as the union’s *members*.

The most basic union type consists of two primitive types:

```typescript
type Union = number | string;

// defined as inline type
function getUserById(id: number | string) {
  // ...
}
```

This allows to handle value which can be _either_ `number` or a `string`. This is great to prove code is type safe for all possible cases!

> In this guide, I'm not going to discuss `null` and `undefined` and how by default they are assignable to anything. Please do yourself a favor and start using [strict](https://www.typescriptlang.org/tsconfig#strict) or at least [strictNullChecks](https://www.typescriptlang.org/tsconfig#strictNullChecks).

## When to use union type?

Union types are perfect to express a finite number of known options, either primitive literals or objects (as discriminated union which we'll discuss those later), where single logic has to handle all the possible cases. A few great examples where union types shine are:

- [finite state machines](https://en.wikipedia.org/wiki/Finite-state_machine) (like React's `useReducer`)
- event names
- object fields (using `keyof` keyword)

You shouldn't use union types where the amount of possible options is too large, for example, a person's name as there is basically infinite number of options. Also keep in mind, they exist only at type level. They are striped out during compilation.

## How to narrow union type?

If your function logic can work most of the time on union type, it's great. But sooner or later you'll need to narrow it down to a specific _union member_. There are few common patterns when narrowing union type.

### Using `typeof` keyword

`typeof` Keyword is the most basic TypeScript tool. Unfortunately, it will work only with `string`, `number` or `function`

```typescript
function getDateFullYear(date: number | Date) {
  if (typeof date === "number") {
    return new Date(date).getFullYear();
  }

  return date.getFullYear();
}
```

### Using `instanceof` keyword

While `typeof` works great with primitives, `instanceof` is great for the OOP feature of TypeScript — classes.

```typescript
class Developer {
  public develop() {
    // ...
  }
}

class Manager {
  public manage() {
    // ...
  }
}

function work(person: Developer | Manager) {
  if (person instanceof Developer) {
    person.develop();
  } else if (person instanceof Manager) {
    person.manage();
  }
}
```

There is more OOP way of implementing `work` function, but above example should do the job as well.

### Using `if` or `switch` statement

Because union types can also consist of literal type members, not generic types but specific values, it's easy to use `switch` or `if` statements on them.

```typescript
function getTextColor(theme: "dark" | "light") {
  switch (theme) {
    case "dark":
      return "#ffffff";
    case "light":
      return "#000000";
  }
}

const textColor = getTextColor("darkk");
// 🛑 Argument of type '"darkk"' is not assignable to parameter of type '"dark" | "light"'.(2345)
```

String literals are really helpful for specifying a limited set of possible options, similar to enum
being a great replacement for them, as there is not that much of added complexity as in enum's case. Using union type of string literals will help not make typos or passing generic `string`.


> When you need to narrow type down from string to string literal, you can use a type guard function, we'll discuss later in this post!

### Using “pattern match” object

When talking about string or number literals, there is great trick allowing to achieve "pattern matching" in TypeScript:

```typescript
function getTextColor(theme: "dark" | "light") {
  return {
    dark: "#fff",
    light: "#000",
  }[theme];
}
```

At first, it may look noisy, but the advantage of this solution is the fact it's an expression not statement, which may be sometimes required in places like in JSX.

### Using type guard function

One nice but advanced feature TypeScript provides us is the ability to define a custom **type guard** function. By default, TypeScript provides us with built-in type guards like `typeof`, `instanceof` keywords we discussed earlier. There is also `Array.isArray()` which is really handy when we need to handle either a single value or multiple values of the same type.

But sometimes it's required to write something more specific to our business logic.
Let's take a look at a simple function that narrows _any_ string to _either_ `dark` or `light`:

```typescript
type Theme = "dark" | "light";

function isTheme(value: string): value is Theme {
  return value === "dark" || value === "light";
}

function getTheme(value: string): Theme {
  if (isTheme(value)) {
    return value;
  }

  // default case
  return "dark";
}
```

This is helpful when your value is coming from outside world (API or used provided) and you cannot be sure it will be always within your expected range.

## Not only primitive types

Union types are not limited to primitive types or type literals. They can as well be objects. I'm using the following pattern all the time as a TypeScript's equivalent of [algebraic data type (ADT)](https://en.wikipedia.org/wiki/Algebraic_data_type). It's a great pattern to express values which may contains different payload or same payload interpreted in different way.

```typescript
type Event = Credit | Debit;
type Credit = { type: "credit"; amount: number };
type Debit = { type: "debit"; amount: number };

let account = 0;
function handleAccountEvent(event: Event) {
  switch (event.type) {
    case "credit":
      account += event.amount;
      break;
    case "debit":
      account -= event.amount;
      break;
  }
}

handleAccountEvent({ type: "credit", amount: "10" }); // account == 10
handleAccountEvent({ type: "debit", amount: "5" }); // account == 5
```

## Conclusion

I hope you understand union types better! With all that knowledge and all the TypeScript features now under your belt, you can take advantage of them when working on the next great feature!

## Resources

List of resources I used when researching this blog post:

- [https://en.wikipedia.org/wiki/Union\_(set_theory)](<https://en.wikipedia.org/wiki/Union_(set_theory)>)
- [https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)
- [https://basarat.gitbook.io/typescript/type-system/discriminated-unions](https://basarat.gitbook.io/typescript/type-system/discriminated-unions)
- [https://camchenry.com/blog/typescript-union-type](https://camchenry.com/blog/typescript-union-type)

---

Read more at [cichocinski.dev](https://cichocinski.dev)