Marius Schulz
Marius Schulz
Front End Engineer

Nullish Coalescing: The ?? Operator in TypeScript

TypeScript 3.7 added support for the ?? operator, which is known as the nullish coalescing operator. We can use this operator to provide a fallback value for a value that might be null or undefined.

#Truthy and Falsy Values in JavaScript

Before we dive into the ?? operator, let's recall that JavaScript values can either be truthy or falsy: when coerced to a Boolean, a value can either produce the value true or false. In JavaScript, the following values are considered to be falsy:

  • false
  • 0
  • -0
  • 0n
  • NaN
  • ""
  • null
  • undefined

All other JavaScript values will produce the value true when coerced to a Boolean and are thus considered truthy.

#Providing Fallback Values with the ?? Operator

The ?? operator can be used to provide a fallback value in case another value is null or undefined. It takes two operands and is written like this:

value ?? fallbackValue;

If the left operand is null or undefined, the ?? expression evaluates to the right operand:

null ?? "n/a";
// "n/a"

undefined ?? "n/a";
// "n/a"

Otherwise, the ?? expression evaluates to the left operand:

false ?? true;
// false

0 ?? 100;
// 0

"" ?? "n/a";
// ""

NaN ?? 0;
// NaN

Notice that all left operands above are falsy values. If we had used the || operator instead of the ?? operator, all of these expressions would've evaluated to their respective right operands:

false || true;
// true

0 || 100;
// 100

"" || "n/a";
// "n/a"

NaN || 0;
// 0

This behavior is why you shouldn't use the || operator to provide a fallback value for a nullable value. For falsy values, the result might not be the one you wanted or expected. Consider this example:

type Options = {
  prettyPrint?: boolean;
};

function serializeJSON(value: unknown, options: Options): string {
  const prettyPrint = options.prettyPrint ?? true;
  // ...
}

The expression options.prettyPrint ?? true lets us provide the default value true in case that the prettyPrint property contains the value null or undefined. If prettyPrint contains the value false, the expression false ?? true still evaluates to false, which is exactly the behavior we want here.

Note that using the || operator here would lead to incorrect results. options.prettyPrint || true would evaluate to true for the values null and undefined, but also for the value false. This would clearly not be intended. I've seen this happen in practice a handful of times, so make sure to keep this case in mind and use towards the ?? operator instead.

#Compiled Output: ES2020 and Newer

The nullish coalescing operator has reached Stage 4 ("Finished") of the TC39 process and is now officially part of ES2020. Therefore, the TypeScript compiler will emit the ?? operator as is without any downleveling when you're targeting "ES2020" (or a newer language version) or "ESNext" in your tsconfig.json file:

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2020"
  }
}

So, this simple expression will be emitted unchanged:

value ?? fallbackValue;

If you're planning on using the ?? operator while targeting "ES2020" or a newer language version, head over to caniuse.com and node.green and make sure that all the JavaScript engines you need to support have implemented the operator.

#Compiled JavaScript Output: ES2019 and Older

If you're targeting "ES2019" or an older language version in your tsconfig.json file, the TypeScript compiler will rewrite the nullish coalescing operator into a conditional expression. That way, we can start using the ?? operator in our code today and still have the compiled code successfully parse and execute in older JavaScript engines.

Let's look at the same simple ?? expression again:

value ?? fallbackValue;

Assuming we're targeting "ES2019" or a lower language version, the TypeScript compiler will emit the following JavaScript code:

value !== null && value !== void 0 ? value : fallbackValue;

The value variable is compared against both null and undefined (the result of the expression void 0). If both comparisons produce the value false, the entire expression evaluates to value; otherwise, it evaluates to fallbackValue.

Now, let's look at a slightly more complex example. Instead of a simple value variable, we're going to use a getValue() call expression as the left operand of the ?? operator:

const value = getValue() ?? fallbackValue;

In this case, the compiler will emit the following JavaScript code (modulo whitespace differences):

var _a;
const value = (_a = getValue()) !== null && _a !== void 0
  ? _a
  : fallbackValue;

You can see that the compiler generated an intermediate variable _a to store the return value of the getValue() call. The _a variable is then compared against null and void 0 and (potentially) used as the resulting value of the entire expression. This intermediate variable is necessary so that the getValue function is only called once.

#Compiled Output: Checking for null and undefined

You might be wondering why the compiler emits the following expression to check the value variable against null and undefined:

value !== null && value !== void 0;

Couldn't the compiler emit the following shorter check instead?

value != null;

Unfortunately, it can't do that without sacrificing correctness. For almost all values in JavaScript, the comparison value == null is equivalent to value === null || value === undefined. For those values, the negation value != null is equivalent to value !== null && value !== undefined. However, there is one value for which these two checks aren't equivalent, and that value is document.all:

document.all === null;
// false

document.all === undefined;
// false

document.all == null;
// true

document.all == undefined;
// true

The value document.all is not considered to be strictly equal to either null or undefined, but it is considered to be loosely equal to both null and undefined. Because of this anomaly, the TypeScript compiler can't emit value != null as a check because it would produce incorrect results for document.all.

You can read more about this curious behavior in an answer to the Why is document.all falsy? question on Stack Overflow. Oh, the things we do for web compatibility.

This article and 44 others are part of the TypeScript Evolution series. Have a look!