Everyday TypeScript: Nullish Coalescing
Welcome to the Nullish Coalescing lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
In a previous lesson, we saw that boolean operators can be dangerous because
0and''are both falsey.Let's review the
numberOrOne(0)case here; that's where the bug lives. Remember that0acts likefalsewhen used in boolean operators.>
function numberOrOne(n: number | undefined): number {return n || 1;}[numberOrOne(3), numberOrOne(undefined), numberOrOne(0)];Result:
[3, 1, 1]
We'd expect
numberOrOne(0)to return0, not1. When we first introduced this problem, we recommended using a linter like typescript-eslint.A linter will catch this bug, which is good! However, correcting this often results in wordy code. For example, here is a version of the
numberOrOnefunction that works as intended and complies witheslint:>
function numberOrOne(n: number | undefined): number {return n === undefined ? 1 : n;}[numberOrOne(3), numberOrOne(undefined), numberOrOne(0)];Result:
[3, 1, 0]
This additional check for
undefinedseems like a small price to pay, but that's only because it's a small, isolated example. In real systems, this overhead can add up.In fact, constantly checking
undefinedand null values is a common problem, often leading to messy code. In React applications, for example, the method above is often used to substitute default values for missing props.The problem gets even worse when the
undefinedcomes from a more complex expression. In thenumbersToWordsfunction below, theundefinedcomes from a function call. We have to store the function's return value in a variable, then test it againstundefinedusing the ternary operator (?:).>
function numberToWord(n: number): string | undefined {if (n === 0) {return 'zero';} else if (n === 1) {return 'one';} else {return undefined;}}function numbersToWords(numbers: number[]): string[] {return numbers.map(n => {const word = numberToWord(n);return word === undefined ? 'some number' : word;});}numbersToWords([1, 2]);Result:
['one', 'some number']
You can see how messy and verbose constantly checking for
undefinedvalues can be.Fortunately, there's a better way to do this. In 2019, ECMAScript added a new feature called "nullish coalescing" designed to make this kind of code shorter and safer. That feature was immediately implemented in TypeScript 3.7.
The nullish coalescing operator is
??. This is similar to the JavaScript logical OR operator,||. But unlike||, it doesn't treat0and''as falsey.Here's a summary of
??'s behavior:1 ?? 'default' === 10 ?? 'default' === 0'a' ?? 'default' === 'a''' ?? 'default' === ''null ?? 'default' === 'default'undefined ?? 'default' === 'default'
We can use this feature to provide default values when something is
nullorundefined. Using??, we can fix our originalnumberOrOnefunction while keeping it terse.>
function numberOrOne(n: number | undefined): number {return n ?? 1;}[numberOrOne(3), numberOrOne(undefined), numberOrOne(0)];Result:
[3, 1, 0]
The
??operator isn't a boolean operator like||; it's only used for checkingnullandundefined. If we runfalse ?? a, we'll get thefalseback, becausefalseisn'tnullorundefined.>
const n: number | null = null;n ?? 1;Result:
1
>
const n: number | undefined = undefined;n ?? 'fallback';Result:
'fallback'
>
const b: boolean = false;b ?? [2];Result:
false
Here's a code problem:
Use the nullish coalescing operator to write a function
nameOrDefaultthat returns a person's name, or 'Wilford' if the name isundefined.function nameOrDefault(name: string | undefined) {return name ?? 'Wilford';}[nameOrDefault('Amir'), nameOrDefault(undefined), nameOrDefault('')];- Goal:
['Amir', 'Wilford', '']
- Yours:
['Amir', 'Wilford', '']
Finally, a note on terminology. Why is this called "nullish coalescing"? The "coalesce" part is old. For example, the 1992 SQL standard introduced a
COALESCEoperator, which takes many arguments and returns the first one that's not null. If you squint, this sort of matches the dictionary definition of "coalesce": "come together to form one mass or whole". Many values go in; one value comes out.Many languages have a "null coalescing operator" similar to the one that we've seen here, and similar to SQL's
COALESCE. Then why is this one called "nullish coalescing", rather than just "null coalescing"? Because JavaScript (and therefore TypeScript) have two kinds of null:nullandundefined. Undefined is kind of like a null: it's null-ish.Putting all of that together, we can read "nullish coalescing" as "returning the first value that isn't null or undefined".