Skip to content

TypeScript

Exhaustive switch cases

How to ensure you exhaust all possible cases in a switch:

ts
function getColorName(c: Color): string {
  switch (c) {
    case Color.Red:
      return "red";
    case Color.Green:
      return "green";
    // Forgot about Blue
    default:
      const exhaustiveCheck: never = c;
      throw new Error(`Unhandled color case: ${exhaustiveCheck}`);
  }
}
  • The never type provides compile-time checking
  • The thrown Error provides runtime checking

Credit: https://stackoverflow.com/a/58009992

Omit from a union type

If you try to Omit from a union type, you probably won't get what you expect. Consider this setup:

ts
interface A {
  foo: string;
  bar: string;
  baz: string;
}

interface B {
  foo: string;
  fizz: number;
  buzz: number;
}

type AorB = A | B;

Then:

ts
type T1 = Omit<AorB, "foo">;
// equivalently:
// type T1 = {};

Why? Let's look at the definition of Omit:

ts
type Omit<T, K extends string | number | symbol> = {
  [P in Exclude<keyof T, K>]: T[P];
};

The problem is that keyof AorB just becomes 'foo' (the intersection of the keys of A and B). So the mapped type only considers that key, and when you omit foo you are left with nothing.

Instead, we can use distributive conditional types to distribute the Omit over the union. The distributive version looks like this:

ts
type DistributiveOmit<T, K extends string | number | symbol> = T extends any ? Omit<T, K> : never;

TS Playground

This work is licensed under CC BY-NC-ND 4.0