Leverage union types in Typescript to avoid invalid states

Typescript has a type system which provides more capabilities than the type system in C# and Java. The one that I found to be very useful is the union type in Typescript. Let's define what a union type is

Union Type

In javascript libraries you might see that a function takes a parameter that can be of multiple different types. For example we have a function in javascript that takes either a number or array of numbers and then appends another number or array of numbers to it

Here we can see that append can take either a number or an array of number but the end result is always an array. So if you have append(1,2) then the result will be [1,2] or in case of append([1,2], 3) the result will be [1,2,3].

How can you represent that type in Typescript?
It can be done using the union type which is just saying that something can be either one thing or the other.

As you can see number | number[] is a union type indicating that arrOrNum could either be of type number or number[]. We can also create a type alias of it such as

type NumberOrArrayOfNumbers = number | number[];

Using union type to avoid invalid states

You can use union types to model various valid states that something can have so that if you accidentally try to create an invalid state, the compiler will throw an error and save you from the trouble of running your application and then encountering it.

Let's take the example of a traffic light with the standard three lights i.e. red, yellow and green. The valid state will be as follows

Traffic lights valid state

If you have a function that gives you the next state of the light then one way of writing it will be

There are multiple problems with it

  1. What if someone passes a traffic light with two invalid lights turned on.
  2. You could accidentally create the next traffic state with multiple invalid lights turned on.
  3. What if all the lights are turned off.

You either need to handle those situations or just trust that this will never happen without any guarantees.

A better way to handle this is

As you see we have leveraged the union types to create a type with all the valid types.
Now you have a guarantee that you will never get the invalid state and nor can you create an invalid state. If you try creating invalid traffic light the compiler will just throw an error. This offloads much of the responsibility to the compiler from the developer.

You can even try changing the traffic light to an invalid state in this playground and see what happens.

Another thing I would like to bring to light is exhaustive checking in Typescript using the never type and union types. Typescript documentation explains it well hence I recommend reading it.