TypeScript

Demystifying TypeScript's Double Question Mark: Nullish Coalescing

Picture of me, Omari
Omari Thompson-Edwards
Thu Jan 25 2024 5 min

In your TypeScript travels, you might have come across many strange, possibly lesser-used operators. One of these is TypeScript's double question mark. In this article I'll explore what that operator does, why to use it, and when.

Nullish Coalescing Operator.

The double question marks you're seeing are the nullish coalescing operator - a logical operator that returns the right operand when the operand is null or undefined (hence the null-ish), and returns the left operand in other cases.

It looks a little like this:

const foo = left ?? right;

If "left" is null or undefined, then the result will be whatever is in "right".

You can also chain as many nullish coalescing operators as you like:

const API_TOKEN = process.env.API_TOKEN ?? process.env.SECRET_API_TOKEN ?? '';

Example - Environment Variables.

A common use-case for these double question marks is assigning default values to variables - in particular variables coming from some external source, such as environment variables.

Imagine some code like this:

const API_TOKEN = process.env.API_TOKEN;
const API_URL = process.env.API_URL;
const API_USERS_PATH = process.env.API_USERS_PATH;
function getUsers() {
    return axios.get(API_URL + API_USERS_PATH + '?token=' + API_TOKEN);
}

A simple API call to fetch some users.  If you're running this imaginary program locally on your machine, you might feel confident that those environment variables will always exist, but TypeScript won't.

Type-safety aside, if we could have a default value for these - that avoids having to provide some of the environment variables altogether, unless you want to override them.

This is where the nullish coalescing operator, those two question marks you've been seeing in TypeScript, come in handy:

const API_TOKEN = process.env.API_TOKEN;
const API_URL = process.env.API_URL ?? 'https://localhost:3001';
const API_USERS_PATH = process.env.API_USERS_PATH ?? '/api/users';
function getUsers() {
    if (!API_TOKEN) throw new Error('API Token not defined');
    return axios.get(API_URL + API_USERS_PATH + '?token=' + API_TOKEN);
}

With one operator, we've given a default value to "API_URL" and "API_USERS_PATH" - pretty handy! You might not want to store a secret API token in code though, so sometimes a default value isn't the best choice, so I've chosen to throw an error if there is no token instead.

The OR Operator.

Let's think through the logic of the nullish coalescing operator. We pick the left side if it isn't null or undefined, otherwise we pick the right-hand side.

If we take a look at the result through a table:

??NullishNot Nullish
NullishNullishNullish
Not NullishNullishNot Nullish

It looks pretty similar to another operator you might be familiar with. We can just change out those double question marks for double vertical bars:

||TruthyFalsy
TruthyTruthyTruthy
FalsyTruthyFalsy

The nullish coalescing operator is nearly identical to the logical OR operator, but "||" returns the right-hand side if the value is any falsy value, not just null or undefined. For a lot of cases this means the difference doesn't matter. One of the cases you do need to watch for are the number 0, which is falsy:

const MAX_LIVES_LEFT = 100;
const score = getLivesLeft() || MAX_LIVES_LEFT;

In this scenario if the player has no lives left, 0 is falsy, so it will resolve to the value on the right.

The other case is empty strings:

function join(strings: string[], joinCharacter: string | null) {
    joinCharacter = joinCharacter || ',';
    return strings.join(joinCharacter);
}

In this function, if you try to join an array with an empty string then that value is falsy, so the result will be the default join character.

The main thing here is to be aware of when the operators are different, and make sure to know if you want to handle values that are just null and undefined, or all falsy values.

Nullish coalescing assignment.

As well as the null coalescing operator, you might also find the less commonly used Nullish coalescing assignment operator used in TypeScript - double question marks followed by equals. As the name implies, it's nullish coalescing combined with assignment:

let var1 = null;
var1 ??= 'Replacing null';
var1 ??= 'Wont pick me (var1 isnt null)'

In the example above, "var1" is null. The first nullish coalescing assignment does store the string in "var1", since "var1" is null. Then the next assignment is skipped, since "var1" is not null.

This can be used to assign a value only if the variable is null. It’s less commonly seen, but you might still run into it.

TypeScript.

The nullish coalescing operator is actually not unique to TypeScript. The double question marks come from JavaScript, but it has some implications for TypeScript. The main addition is type narrowing. Let's take a function that can return a nullish value:

function maybeNull() {
    if (Math.random() > 0.5) return 'foo';
    return null;
}

The return type for this is "foo" | null. If you want to use that value somewhere, usually you'd need to check if the return type is null first:

const foo = maybeNull();
if (foo) console.log(foo.length);

Or you can use the nullish coalescing operator to remove null from the type union:

const foo = maybeNull() ?? '';
console.log(foo.length);

Conclusion.

Thanks for reading! Hopefully, with this article, you now know what those double question marks mean in TypeScript, and you're also able to use the nullish coalescing operator in your own code.  It's a very useful tool for creating more concise, safer TypeScript code, and dealing with potential null or undefined values in a quick and easy way.
For a quick summary of everything:

  • Double question marks in TypeScript means the nullish coalescing operator. If the left-hand side operand is undefined or null, it returns the left-hand side, otherwise it returns the right-hand side operand
  • It's identical to the logical OR operator, but the logical OR operator handles all falsy values, not just null or undefined
  • The main case where this matters is for 0  or empty strings, which are falsy
  • Use the operand when you want to check for null or undefined and provide default values in those cases

Thanks for reading! If you liked this article, feel free to share it!

read more.

Me
Hey, I'm Omari 👋

I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, feel free to reach out.