DEV Community

Cover image for TypeScript Types Deep Dive - Part 2: The Absence of value
Jaime González García
Jaime González García

Posted on • Updated on • Originally published at barbarianmeetscoding.com

TypeScript Types Deep Dive - Part 2: The Absence of value

This article was originally published on Barbarian Meets Coding.

TypeScript is a modern and safer version of JavaScript that has taken the web development world by storm. It is a superset of JavaScript that adds in some additional features, syntactic sugar and static type analysis aimed at making you more productive and able to scale your JavaScript projects. This is the second part of a series of articles where we explore TypeScript's comprehensive type system and learn how you can take advantage of it to build very robust and maintainable web apps.

Haven't read the first part of this series? If you haven't you may want to take a look. There's lots of interesting and useful stuff in there.

JavaScript and the absence of value

null is often referred to as The Billion Dollar Mistake. In the words of Tony Hoare who first introduced it in ALGOL in 1965:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Tony Hoare and The Billion Dollar Mistake

Let's illustrate this pain with a simple example (although I'm pretty sure that you're likely familiar with it and have experienced it many times).

Imagine we have such a function that allows us to destroy our evil enemies:

// Destroy Thy Enemies!!
function attack(target) {
  target.hp -= 10;
}
Enter fullscreen mode Exit fullscreen mode

Hmm... It's Christmas. The time of joy, happiness and love. So let's switch the example for something more suitable:

// Love!!!!!
function hug(target) {
  target.happiness += 1000;
}
Enter fullscreen mode Exit fullscreen mode

Now THAT is much better! So imagine that we want to take advantage of the hug function to spread some love around the world:

// Love!!!!!
function hug(target) {
  target.happiness += 1000;
}

const sadJaime = {name: 'jaime', happiness: 0};
hug(sadJaime); // => Wihooo! ❤️
Enter fullscreen mode Exit fullscreen mode

Great. Everything is going according to plan. We've made one person happier. Yippi! But what if we make our sample a little more convoluted?

Let's say that we want to go ahead and hug everyone. Like EVERYONE in the world. We have this magic Map that has been pre-populated (probably by Santa) with every single human being alive on the planet right now. So we verify that it does indeed work as marketed:

// Love!!!!!
function hug(target) {
  target.happiness += 1000;
}

const sadJaime = magicMap.get("Jaime"); // that's me of course
hug(sadJaime); // => Wihooo! ❤️
Enter fullscreen mode Exit fullscreen mode

Excellent! But what if the person we're trying to find doesn't exist or isn't alive on the planet right now?

// Love!!!!!
function hug(target) {
  target.happiness += 1000;
}

// returns undefined because Eleanor is in The Good Place
const eleanor = magicMap.get("Eleanor Shellstrop"); 

// brace yourself
hug(eleanor); // => 💥💥💥 Much explosion. 
// Cannot read property happiness of undefined
Enter fullscreen mode Exit fullscreen mode

Yep. That's the problem right there. JavaScript just like Java, Ada and ALGOL suffers from the same ailment caused by the presence of null. Although in the case of JavaScript, it may be worse because we have not one but two ways to represent the absence of value: null and undefined.

Brendan Eich feeling awesome with sun glasses as he puts both null and undefined into JavaScript

Both of these keywords represent the absence of value in JavaScript. Both will result in a null reference exception1 like the one described in the example above. So What is the difference between them?

There are several:

  • As far as I know there's no native browser API that ever returns null. For the web platform, the absence of value is always undefined.
  • By convention, the community usually refers to null as the absence of value, whereas undefined represents something that hasn't yet been defined. A developer could use null to denote a deliberate absence of value, whereas undefined would be the web platform telling you that something hasn't been defined yet. null may be also produced by APIs when interoperating between clients and servers (e.g. Java sending JSON to a JavaScript front-end). In fact, the JSON spec only supports null and not undefined.
  • Default arguments behave differently when given undefined or null. Pass an undefined as an argument to your function and your function will use the default value that you provide. Pass a null instead and you function will use it happily instead of your default value. (Which is mighty dangerous)

For all of the above, I try to steer away from null as much as I can, and handle undefined when it's produced by the platform. But even so, there will be times when you'll have no other choice that to work around null and undefined. And they will inevitably lead you to null reference exceptions and the very classic undefined is not a function...

So, How can TypeScript help us with this mess?

TypeScript and the absence of value

So TypeScript doesn't have two ways to represent the absence of value. It has four.

Anders Hejlsberg feeling awesome with sun glasses as he puts both null, undefined, void and never into TypeScript

These four ways to represent the absence of value are:

  • undefined and null just like in JavaScript since, after all, TypeScript is a superset of JavaScript
  • void which represent the return type of a function that doesn't return anything
  • never which represents the return type of a function that never returns (e.g. it throws an exception instead)

Wow! Four instead of Two?? Is this something good? And the answer is YES. Not because of the numerous ways to represent the absence of value but because in TypeScript null and undefined are also types.

So What?

Let's take a look at the previous example. We define the Target interface to represent anything that has a happiness property (which is all a human, pet or monster needs to be huggable):

interface Target {
  happiness: number;
}
Enter fullscreen mode Exit fullscreen mode

And now we can use it in our hug function:

// Love!!!!!
function hug(target: Target) {
  target.happiness += 1000;
}
Enter fullscreen mode Exit fullscreen mode

If we try to hug sad Jaime as we did earlier everything shall be fine:

// Love!!!!!
function hug(target: Target) {
  target.happiness += 1000;
}

const sadJaime = magicMap.get("Jaime"); // that's me of course
hug(sadJaime); // => Wihooo! ❤️
Enter fullscreen mode Exit fullscreen mode

But what if we make the attempt to hug Eleanor?

// Love!!!!!
function hug(target: Target) {
  target.happiness += 1000;
}

// returns undefined because Eleanor is in The Good Place
const eleanor = magicMap.get("Eleanor Shellstrop"); 

hug(eleanor); // => 💥 Argument of type 'undefined' is not assignable to parameter of type 'Target'.
Enter fullscreen mode Exit fullscreen mode

We get an error!! We get a compile-time error. That is, as soon as we type the code above the TypeScript compiler will tell us that we're doing something wrong. Because the function hug expects a Target and we're giving it undefined which is a completely different type that doesn't have any of the characteristics of Target, the TypeScript compiler jumps in to help.

And so this is how TypeScript takes advantage of the null and undefined types to prevent you from running into null reference exceptions and the dreaded million dollar mistake. Awesome, right?

Strict Null Checking

The behavior we've just seen, where TypeScript will prevent you from shooting yourself in the foot with null or undefined is called strict null checking. It is a feature of the TypeScript compiler which was added in TypeScript 2.0 an which needs to be enabled in your tsconfig file via the stricNullChecks setting. In general, it is strongly advised to aspire to having your TypeScript configuration be as strict as possible. That way you'll take the most advantage of TypeScript's type system and you'll be able to write safer and more maintainable applications.

Just for kicks. How could we rewrite the function above to allow for null or undefined and be equivalent to the JavaScript version? One way to do it would be the following:

// Love!!!!!
function hug(target: Target | undefined | null) {
  target.happiness += 1000;
}
Enter fullscreen mode Exit fullscreen mode

So we're explicitly telling TypeScript that the argument of that function can be either Target, undefined or null by using a type union. Or alternatively:

// Love!!!!!
function hug(target?: Target | null) {
  target.happiness += 1000;
}
Enter fullscreen mode Exit fullscreen mode

Where we use an optional argument using the target? notation which is a shorthand for Target | undefined.

Working with Null or Undefined

There'll be some situations in which you'll still need to work with null or undefined. Let's say that you're working with a third party library that is implemented in JavaScript and doesn't care much about returning null or undefined. You potentially have a mighty warrior that is about to use its sword to slash some potatos for the Christmas dinner, so you write this:

const warrior = tavern.hireWarrior({goldCoins: 2});

if (warrior !== null 
    && warrior !== undefined
    && warrior.sword !== null 
    && warrior.sword !== undefined){
  warrior.sword.slash();
}
Enter fullscreen mode Exit fullscreen mode

The warrior hiring API you were using was written in JavaScript and you can't be sure if it even returns a warrior, or if the warrior has a sword so to be on the safe side you are forced to write a bunch of null/undefined checks.

TypeScript (>=3.7) has an alternative and less wordy version to the pattern above. The optional chaining operator ?. which lets us rewrite the above example:

warrior?.sword?.slash();
// The ? is often called the Elvis operator
// because this ?:-p
Enter fullscreen mode Exit fullscreen mode

That is, we shall call the slash method only when the warrior and her sword are not undefined nor null. That's a much nicer alternative if you ask me.

Yet another alternative to solving the issue with the hiring a warrior at a disreputable tavern is to have a default value at hand that we can use when things go awry. So in order to make sure that the slashing above takes place we could've followed the approach of always making sure that the mighty warrior does exist:

let warrior = tavern.hireWarrior({goldCoins: 2});
if (!warrior) {
  warrior = new Warrior('Backup Plan Joe');
}
if (!warrior.sword) warrior.equip(new RustySword());

warrior.sword.slash();
Enter fullscreen mode Exit fullscreen mode

TypeScript also has an alternative to the check-if-this-is-null-and-if-it-is-assign-this-other-value pattern: the nullish coallescing operator ??.

Using ?? we can simplify the example above:

let warrior = tavern.hireWarrior({goldCoins: 2}) 
              ?? new Warrior('Backup plan Joe');

if (!warrior.sword) warrior.equip(new RustySword());

warrior.sword.slash();
Enter fullscreen mode Exit fullscreen mode

Nice right? Both ?. and ?? operators make it that much easier to work around the absence of value in TypeScript.

Optional chaining and the nullish coalescing operator were approved as part of ES2020 and are now official features of the JavaScript language. Yey!

Are There Better Ways to Model the Absence of Value?

A really cool way to model the absence of value comes from functional programming in the form of the Maybe monad. In later articles of the series when we've dabbled in the mysteries of generic types we'll revisit this topic and learn about how TypeScript can support these functional programming patterns.

In Summary

null and undefined can cause havoc in your JavaScript programs. They can break things willy nilly at runtime, or they can force you into writing a lot of guard causes and defensive code that can be very verbose and obscure your core business logic.

In TypeScript null and undefined are also types. This in combination with strict null checks allows you to protect your programs from null reference exceptions at compile time. So that, as soon as you make a mistake that could have resulted in a bug sometime along the line at runtime, you get immediate feedback and can fix it right away.

When confronted with the need to work with null or undefined TypeScript 3.7 and ES2020 have two features that can lessen your woes. Optional chaining and the nullish coallescing operator will save you a lot of typing when dealing with null and undefined.

And that's all for today. Hope you've enjoyed the article and learned something new. Until next time, take care and have a wonderful day.


  1. In JavaScript null reference exceptions are treated as the more general TypeError. There isn't a specific error that only applies to null reference exceptions like in other languages such as C# or Java (NullReferenceException). 

Top comments (4)

Collapse
 
256hz profile image
Abe Dolinger

Nice writeup! Just a small nitpick: the optional chaining operator is actually ?., which you'll need to use when you're checking for a property with bracket notation, i.e. thing?.[property]. If you write thing?[property] it'll try to evaluate it as a ternary and become sad.

Collapse
 
vintharas profile image
Jaime González García • Edited

Thank you! And thanks for spotting that! 😄

Collapse
 
thepeoplesbourgeois profile image
Josh

I can hug you, sadJaime

Collapse
 
vintharas profile image
Jaime González García

🤗