Writing exhaustive switch statements in TypeScript
As long as pattern matching is not yet part of TypeScript, developers sometimes need to get creative to avoid a common pitfall in the language: how do you make sure a switch statement has covered all the variants in a union type?
Today, I’d like to share my favorite approach.
Let’s look at the following code example:
type WrittenNumber = "one" | "two" | "three";
function printAsDigit(number: WrittenNumber) {
let digit;
switch (number) {
case "one":
digit = 1;
break;
case "two":
digit = 2;
break;
case "three":
digit = 3;
break;
}
console.log(`The number is ${digit}.`);
}
Looks innocent enough, right? But what if we added the variant “four” to WrittenNumber
? Ideally, we would like it if TypeScript could catch it when we forget to update the switch
statement. Can we make it so?
If you’re using typescript-eslint
, there’s a rule that gets you covered: https://typescript-eslint.io/rules/switch-exhaustiveness-check/
But if you’re not using ESLint, are you out of luck?
Not necessarily! With a simple refactor we can make the code look a bit cleaner and use TypeScript itself to our advantage. Look at this version:
type WrittenNumber = "one" | "two" | "three";
function printAsDigit(number: WrittenNumber) {
const digit = numberToDigit(number);
console.log(`The number is ${digit}.`);
}
function numberToDigit(number: WrittenNumber): number {
switch (number) {
case "one":
return 1;
case "two":
return 2;
case "three":
return 3;
}
}
Not only have we saved ourselves from the ugly break
statements, and replaced a let
variable with const
(generally, if you can do with fewer mutable variables, that’s preferred!), we have also increased our type safety. The trick is that numberToDigit()
is explicitly annotated as returning a number
. Because of this, if the switch
statement missed a variant, TypeScript will warn us!
Only caveat: If one of the cases in a switch statement has a legit reason for producing undefined
, the type safety benefit will be lost.
But even then I really like the added elegance of putting switch statements in their own functions. Maybe I could make a Biome rule to enforce this as a pattern? 🤔
Let me know your thoughts on Mastodon!
Comments are generated from replies to this Mastodon post. Reply to the post to have your own comment appear.