Typescript `satisfies never`: Exhaustiveness checking in Typescript
Tags: satisfies, typescript
Published: 2022-11-19 00:09:11

---
title: Typescript `satisfies never`: Exhaustiveness checking in Typescript
published: true
description: An emerging pattern to eliminate errors-of-omission when working with unions and discriminated unions. 
tags: satisfies, typescript 
# cover_image: ![Clipboard with every box ticked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9dc3xxw7mfwww987hmx.jpg)
# Use a ratio of 100:42 for best results.
# published_at: 2022-11-18 20:49 +0000
---

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rej5lcug2ymoe8qdazi.jpg)

## Use Case: Handle every case of a union  

Exhaustively handling every case of a union is a common pattern needed in a Typescript codebase. This could be a simple 'string literal' union like...

```typescript
type Flavour = "vanilla" | "chocolate" | "mint";
```

...or a more complex [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions). 

> Note: This article describes a specific and valuable use case for `satisfies` - for exhaustiveness checking in unions. If you need a background introduction to the operator, you can see [my Medium article on satisfies](https://medium.com/@cefn/typescript-satisfies-6ba52e74cb2f) .

## Discriminated Unions

In a discriminated union a named property of an object is used to differentiate between distinct shapes that an object's state can take. In the example below, different shapes have a different value of the `kind` property. 

Code paths at runtime will use `item.kind ===` to check the shape of an item before processing it. But what if we forget to handle one of the different shapes? Is the only way to handle this throwing an Exception at runtime? Surely that's too late!

## Satisfies to the rescue

Typescript can check which cases you have handled at __compile time__. It can ensure we don't find out the hard way at runtime that we forgot to handle one of the valid shapes of an object's state.

I was really [pleased](https://github.com/microsoft/TypeScript/issues/47920#issuecomment-1042728706) to see the `satisfies` keyword [added](https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/#satisfies) to the Typescript language. It has enabled a whole load of new code patterns, and one of them is checking exhaustiveness of unions - that you haven't missed a case.

## Worked Example: The 'Tech Layoffs' Game 

As a worked example, let's define a user action in an imaginary 'Tech Layoffs' game. 

In the game you first have to run around the office, trying to find any co-workers who have been forced to come into the office today, and say goodbye to them before they are summarily fired. 

```typescript
type Action =
  | {
      kind: "runaround";
      speed: number;
    }
  | {
      kind: "saygoodbye";
      phrase: "Hasta la vista" | "I'll be back";
    }
```

> I use `kind` as a discriminating property if nothing else comes to mind. The word `type` already has a meaning in Typescript.

Each action structure has a distinct payload, so you can't write code that accesses the `phrase` property of a `"runaround"` action or the `speed` property of a `"saygoodbye"` action. 

In fact the union itself has neither `speed` nor `phrase` properties, because those properties are not defined on all members of the union. The `kind` property is the only one in common.

Let's write an action handler which exploits the property in common, and allows Typescript to reason about which kind of action we are dealing with.

```typescript
function handleAction(action: Action) {
  const { kind } = action;
  if (kind === "runaround") {
    setSpeed(action.speed);
    return;
  }
  if (kind === "saygoodbye") {
    say(action.phrase);
    return;
  }
}
```

It handles all the cases - you checked. Both paths compile and can access a number or string to call `setSpeed` and `say` respectively. So far so good. 

However, it really **isn't** good enough for an enterprise codebase. You want contributors to be able to make changes 2 years from now without having to trace every possible implicit bug.

## The failure case

![Spaghetti oops - photo by Neal Fowler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh49e6x7trktzdjymnss.jpg)

The game designer is getting really clear user feedback from the players of "Tech Layoffs". The users know the rules, they know the game and they're going to play it. But honestly they don't like the endless cycle of fearful running and saying farewell to departing colleagues around a soulless maze of cubicles.

As new feature work, an exit journey is added to the game. The type was extended with a `"giveup"` case...

```typescript
type Action =
  | {
      kind: "runaround";
      speed: number;
    }
  | {
      kind: "saygoodbye";
      phrase: "Hasta la vista" | "I'll be back";
    }
  | {
      kind: "giveup";
      reason: "Too difficult" | "Made me cry";
    }
```

But...oh no, the corresponding change wasn't made to our handler. It's silently ignoring one of the Actions in the game, and the change raised no compiler errors! 

Wouldn't it be nice to have the handler's exhaustiveness enforced by the compiler. Well since Typescript 4.9 there is a really elegant way!

The example below shows a handler that breaks as soon as anyone adds a case to the union which isn't handled, by simply adding the line `kind satisfies never` (and maybe a helpful comment). 

```typescript
function handleAction(action: Action) {
  const { kind } = action;
  if (kind === "runaround") {
    setSpeed(action.speed);
    return;
  }
  if (kind === "saygoodbye") {
    say(action.phrase);
    return;
  }
  // every `kind` already handled
  // line should never be hit
  kind satisfies never;
}
```

Typescript can reason about the control flow as our procedure eliminates values from the union because we `return` when any value is matched. 

The first `if` clause eliminated `"runaround"` in this way. The second eliminated `"saygoodbye"`. The line `kind satisfies never` then acts as a guard that there are no further values.

If every value has been eliminated then the value of `kind` **can** indeed be assigned to type `never`. That is to say the control flow analysis has eliminated that path - there's no further value that `kind` could ever be. This handler is never going to let you down.

## How to know what's been going on?

Here you can see the presentation of the compiler error in an editor. Since we added an extra action, it warns you that there's a possible value of `kind` that might reach the guard.

![Never gonna give you up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qkommj11qx72hl53bfu.png)

Finally the fix is easy - ensuring exhaustiveness makes the redline go away...

![Never gonna let you down](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qwql9kk5kciinhiwb7r.png)

## You wouldn't get this with any other guy

For those who read this far, I'm open to work, and you can see my CV at https://cefn.com/cv