Skip to content
Theory · Unions and literal types · lesson 3 of 8

null and undefined in unions

Let's go back to the parking lot. Now the sign says "A car—or the spot is empty". Before you open the trunk, you need to make sure a car is parked there at all: an empty spot has nothing to open.

Programs have missing values too. A function looks up a user's city and does not always find it. In JavaScript such a function often returns null. In strict mode null is a type of its own and does not fit into string. So you have to tell TypeScript directly: the result is a string or null.

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(2);
console.log(city.toUpperCase()); // ✗
TEXT
error TS18047: 'city' is possibly 'null'.

Let's walk through the example line by line:

  1. : string | null after the parentheses with the parameters says that the function returns a string or null.
  2. For user 1 the function returns "Kazan", for everyone else null.
  3. const city = findCity(2) gets the type string | null: TypeScript takes it from the function's declaration.
  4. city.toUpperCase() is an error: tsc reports that city may hold null.

If this JavaScript ran, the program would crash with TypeError: Cannot read properties of null (reading 'toUpperCase'). TypeScript shows the mistake before the run.

A union with null

string | null is an ordinary union type. Only its second option means "no value". It is the "a car or empty" sign: you cannot call a string method on a value of this type until null is ruled out.

undefined works the same way. The type string | undefined means "a string, or maybe nothing was set".

This check is turned on by the strictNullChecks flag, which is part of the strict mode. Without it, null and undefined fit into any type, tsc stays silent, and the error is back to showing up only at run time.

Interviewers like to ask this: "What does strictNullChecks give you?" The answer: null and undefined stop fitting into other types, and the compiler makes you handle them explicitly.

The ?. and ?? operators and types

The JavaScript operators ?. and ?? work in TypeScript too. The compiler understands what they do and takes it into account in the result's type:

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(2);
const upper = city?.toUpperCase();
const shown = city ?? "—";
console.log(upper, shown); // undefined —

Hover over upper and shown in the editor, and the hover shows their types. Let's take each operator in turn.

The ?. operator

city?.toUpperCase() calls the method only if city is neither null nor undefined. Otherwise the whole expression gives undefined. That is why upper has the type string | undefined: a string if the city was found, undefined if not. Here the city was not found, and the program prints undefined.

The ?? operator

city ?? "—" takes the right side when the left side is null or undefined. Either way you get a string: the city itself or "—". That is why shown has the type string, and null is gone from it. The program prints —.

Common mistake

The result of ?. is treated as a plain string:

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(1);
const upper: string = city?.toUpperCase(); // ✗
TEXT
error TS2322: Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.

The ?. operator does not remove the "empty" option; it turns null into undefined. The second line of the message says exactly that: undefined does not fit into string. It does not matter that findCity(1) actually returns a string: the compiler looks at the type. To get a string, add a default value with ??:

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(1);
const upper: string = city?.toUpperCase() ?? "—";
console.log(upper); // KAZAN

Key points

  • The type string | null or string | undefined says honestly that the value may be missing.
  • Until null is ruled out, you cannot call a string method on such a value: tsc reports 'city' is possibly 'null'.
  • This check is turned on by strictNullChecks, part of strict mode.
  • city?.toUpperCase() gives string | undefined, while city ?? "—" gives string.