Skip to main content

Kill two bugs with one type 🪲

I’ve always liked strong typing in programming. It fits the way my brain is wired, I guess. However, not everyone likes strong typing. If you don’t, you can be content in knowing that there will only be a single type annotation in this blog post. 😉

A bug getting crushed by a rock.

I recently stumbled upon what I found to be a pretty nice example of how just a little typing can help avoid several real-world problems. It also showcases a nice feature of TypeScript’s type system. I liked so much that I wanted to share it.

The type checker to the rescue #

Let’s say we have an application where all user-visible strings have been extracted to a translation object. It has a key for every string that is needed in our application, and the values are the actual translated string.

In a simplified JavaScript example, a variable en holds our English translations.

const en = {
  HELP: "Help",
  CANCEL: "Cancel",

Let’s then say we want to use those strings in our application.

console.log(en.CANCL) // oops... should be CANCEL

This example contains a typo – instead of en.CANCEL, we have en.CANCL. In plain JavaScript, this would not be discovered until runtime. And even then, we will just print “undefined” rather than throwing an error.

By running TypeScript’s type checker on the code, we can turn this into a compilation error. We can discover it long before it has to reach production! And we did not even have to add any type annotation.

With a little help from a type #

Time to add another language. We want to add Swedish translations too.

const sv = {
  HELP: "Hjälp"

That was easy! But no, I forgot to translate CANCEL. The two translations are out of sync. TypeScript will not save us here, because it does not know that sv is supposed to have the same keys as en. But we can tell it!

const sv: typeof en = {
  HELP: "Hjälp",
  CANCEL: "Avbryt"

We added a type annotation for sv which says typeof en. It is a pretty cool TypeScript feature to say that whatever type en has, sv should have too! Think of it as the type checker equivalent of saying “follow that car”!

The TypeScript type checker will enforce that the object structure of sv exactly matches that of en. They should have the exact same keys, and their values should be of the same types. If you add or remove a translation in either en or sv but not the other, you will get a compilation error!

Make invalid states unrepresentable #

We have effectively removed two types of possible runtime errors! It is simply impossible to refer to a translation which does not exist, and it is impossible for the translations to be out of sync with each other.

This is an example of what is sometimes called “make invalid states unrepresentable”. The idea is to model data, and use appropriate types, so that it is impossible to construct or even express an invalid state.

We also saved a bunch of work. We did not have to write tests to detect these kinds of problems. Nor will we have to spend time on troubleshooting these problems in production at some point in the future. They simply cannot happen.

While this is a simple example, it is still practically useful. It also nicely demonstrates how strong type systems can help eliminate certain types of errors, and reduce the work needed to maintain the solution.