Execute Program

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 0 and '' are both falsey.

  • Let's review the numberOrOne(0) case here; that's where the bug lives. Remember that 0 acts like false when used in boolean operators.

  • >
    function numberOrOne(n: number | undefined): number {
    return n || 1;
    }
    [numberOrOne(3), numberOrOne(undefined), numberOrOne(0)];
    Result:
    [3, 1, 1]Pass Icon
  • We'd expect numberOrOne(0) to return 0, not 1. 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 numberOrOne function that works as intended and complies with eslint:

  • >
    function numberOrOne(n: number | undefined): number {
    return n === undefined ? 1 : n;
    }
    [numberOrOne(3), numberOrOne(undefined), numberOrOne(0)];
    Result:
    [3, 1, 0]Pass Icon
  • This additional check for undefined seems 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 undefined and 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 undefined comes from a more complex expression. In the numbersToWords function below, the undefined comes from a function call. We have to store the function's return value in a variable, then test it against undefined using 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']Pass Icon
  • You can see how messy and verbose constantly checking for undefined values 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 treat 0 and '' as falsey.

  • Here's a summary of ??'s behavior:

    • 1 ?? 'default' === 1
    • 0 ?? 'default' === 0
    • 'a' ?? 'default' === 'a'
    • '' ?? 'default' === ''
    • null ?? 'default' === 'default'
    • undefined ?? 'default' === 'default'
  • We can use this feature to provide default values when something is null or undefined. Using ??, we can fix our original numberOrOne function while keeping it terse.

  • >
    function numberOrOne(n: number | undefined): number {
    return n ?? 1;
    }
    [numberOrOne(3), numberOrOne(undefined), numberOrOne(0)];
    Result:
    [3, 1, 0]Pass Icon
  • The ?? operator isn't a boolean operator like ||; it's only used for checking null and undefined. If we run false ?? a, we'll get the false back, because false isn't null or undefined.

  • >
    const n: number | null = null;
    n ?? 1;
    Result:
    1Pass Icon
  • >
    const n: number | undefined = undefined;
    n ?? 'fallback';
    Result:
    'fallback'Pass Icon
  • >
    const b: boolean = false;
    b ?? [2];
    Result:
    falsePass Icon
  • Here's a code problem:

    Use the nullish coalescing operator to write a function nameOrDefault that returns a person's name, or 'Wilford' if the name is undefined.

    function nameOrDefault(name: string | undefined) {
    return name ?? 'Wilford';
    }
    [nameOrDefault('Amir'), nameOrDefault(undefined), nameOrDefault('')];
    Goal:
    ['Amir', 'Wilford', '']
    Yours:
    ['Amir', 'Wilford', '']Pass Icon
  • Finally, a note on terminology. Why is this called "nullish coalescing"? The "coalesce" part is old. For example, the 1992 SQL standard introduced a COALESCE operator, 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: null and undefined. 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".