How to work with union types that can be undefined

Ever struggled with figuring out how to deal with undefined? Or spent a good cup of coffee (and a half) to stop the Typescript compiler from screaming at you?

In Typescript we often come across values that can have multiple values, called union types, and in some cases one of those values is undefined.

If your first instinct is thinking “well can’t I just change the type?”, reading on might be worth it 😉

Sure, we can change the type. Provided we have full control over the type in the first place. But what if it comes from an API or a library we’re using? These cases make it hard to change the type(s) of the data coming in, and I’d argue that’s generally a bad practice too, because those types are trying to tell you something.

Let’s say the following type is provided by a library we have, for connecting to an external API:

type Users = users?: {
  id: number;
  email: string;
}[] | undefined

What might we do?

My Favorite Solution

The best solution, in my opinion, is not to change the incoming type, but instead accept that it could be undefined and deal with it as such.
Not respecting a potential undefined could cause unwanted errors and behaviours, simply because types don’t do anything at runtime, and so overwriting a type at compile time, is effectively just hiding an issue that might arise at runtime.

Instead what we should do, is simply check if the value is undefined or not. Because else is smelly, I generally prefer checking the negative case, something like this:

if (!users) return;

Depending on how your codebase is structured or what you want to do, you could throw an error here instead of simply returning.

The cool thing about this approach is that Typescript is clever enough to know that users can now no longer be undefined because we checked for that case in our control flow and we don’t have to wrap our logic in an else clause.

The array can still be empty though, so you might want to check for that, as well as enforcing any other checks to ensure that your code runs as smoothly as it should be doing.

That’s it! Your code is now both type safe at compile time and at runtime, making sure your compiler stops screaming and your server won’t break.