ZetCode

TypeScript null and undefined

last modified October 15, 2026

JavaScript has two values for "no value": undefined and null. A variable that has not been assigned, a missing object property, and a function without a return statement all produce undefined. The null value is usually assigned on purpose to mark an empty value. Accessing a property of either of them is one of the most common runtime errors in JavaScript. TypeScript prevents it with the strictNullChecks option.

undefined and null

The two values have their own types, and typeof reports them differently.

values.ts
let notAssigned: string | undefined;
const empty: null = null;
const user: { name: string; email?: string } = { name: "Alice" };

console.log(notAssigned);
console.log(empty);
console.log(user.email);

console.log(typeof undefined);
console.log(typeof null);
console.log(null == undefined, null === undefined);

The typeof null expression returns "object", a long-standing quirk of JavaScript. The loose equality == treats null and undefined as equal, the strict equality === does not.

$ npx tsx values.ts
undefined
null
undefined
undefined
object
true false

strictNullChecks

With the strictNullChecks option, which is part of strict, null and undefined are not valid values of other types. A value that may be missing must say so in its type.

strict_null.ts
let city: string = "Bratislava";
city = null;

function findUser(id: number): string | undefined {
    return id === 1 ? "Alice" : undefined;
}

const user = findUser(2);
console.log(user.toUpperCase());

Assigning null to a string is an error, and so is calling a method on a value that may be undefined. Without strictNullChecks, both lines compile and the second one crashes at runtime.

$ npx tsc --noEmit
strict_null.ts(2,1): error TS2322: Type 'null' is not assignable to type 'string'.
strict_null.ts(9,13): error TS18048: 'user' is possibly 'undefined'.

Narrowing

A value of a union type with null or undefined is narrowed with a check, after which it can be used safely.

narrowing.ts
function greet(name: string | null | undefined): string {
    if (name == null) {
        return "Hello, guest!";
    }

    return `Hello, ${name.toUpperCase()}!`;
}

console.log(greet("alice"));
console.log(greet(null));
console.log(greet(undefined));

The name == null check is true for both null and undefined. This is the one common case where the loose equality is preferred. After the check, name has the string type.

$ npx tsx narrowing.ts
Hello, ALICE!
Hello, guest!
Hello, guest!

Optional properties and parameters

A question mark makes a property or parameter optional. Its type then includes undefined.

optional.ts
type Contact = {
    name: string;
    phone?: string;
};

function format(contact: Contact, prefix?: string): string {
    const phone = contact.phone !== undefined ? contact.phone : "no phone";
    const start = prefix !== undefined ? `${prefix} ` : "";

    return `${start}${contact.name}: ${phone}`;
}

console.log(format({ name: "Alice", phone: "555-0101" }));
console.log(format({ name: "Bob" }, "Mr."));

Inside the function, contact.phone has the type string | undefined and must be checked before it is used as a string.

$ npx tsx optional.ts
Alice: 555-0101
Mr. Bob: no phone

Optional chaining

The optional chaining operator ?. stops the evaluation and returns undefined when the value on its left is null or undefined.

optional_chaining.ts
type Company = {
    name: string;
    address?: {
        city: string;
        zip?: string;
    };
    getCeo?: () => string;
};

const acme: Company = {
    name: "Acme",
    address: { city: "Bratislava" },
};

const startup: Company = { name: "Startup" };

console.log(acme.address?.city);
console.log(startup.address?.city);
console.log(acme.address?.zip?.length);
console.log(startup.getCeo?.());

The operator works with property access and with method calls, written as ?.(). The type of startup.address?.city is string | undefined.

$ npx tsx optional_chaining.ts
Bratislava
undefined
undefined
undefined

Nullish coalescing

The nullish coalescing operator ?? returns its right operand when the left one is null or undefined. Unlike ||, it keeps other falsy values such as 0 and the empty string.

nullish.ts
type Settings = {
    volume?: number;
    title?: string;
};

const settings: Settings = { volume: 0, title: "" };

console.log(settings.volume || 50);
console.log(settings.volume ?? 50);

console.log(settings.title || "Untitled");
console.log(settings.title ?? "Untitled");

const empty: Settings = {};
console.log(empty.volume ?? 50);

A volume of 0 is a valid setting, but || replaces it with the default value. The ?? operator only replaces missing values. The ??= operator assigns a value only if the variable is null or undefined.

$ npx tsx nullish.ts
50
0
Untitled

50

The non-null assertion operator

The postfix ! operator tells the compiler that a value is not null or undefined. It removes these types without any check at runtime.

non_null.ts
const scores = new Map<string, number>([
    ["Alice", 92],
    ["Bob", 78],
]);

const alice = scores.get("Alice")!;
console.log(alice + 5);

const carol = scores.get("Carol")!;
console.log(carol + 5);

The get method returns number | undefined. For Alice, the assertion is correct. For Carol, it is wrong: the value is undefined and the result is NaN, with no error from the compiler. The operator should be used only when the value is guaranteed by logic the compiler cannot see; a check with has or ?? is usually better.

$ npx tsx non_null.ts
97
NaN

Array lookups

Methods such as find return undefined when nothing is found, and their return type reflects this.

find.ts
type Product = {
    id: number;
    title: string;
};

const products: Product[] = [
    { id: 1, title: "Keyboard" },
    { id: 2, title: "Mouse" },
];

function titleOf(id: number): string {
    const product = products.find(p => p.id === id);
    return product?.title ?? "unknown product";
}

console.log(titleOf(2));
console.log(titleOf(7));

The product variable has the type Product | undefined. Optional chaining and nullish coalescing handle the missing case in one expression. Note that indexing an array, as in products[7], has the type Product by default; the noUncheckedIndexedAccess option adds undefined to it.

$ npx tsx find.ts
Mouse
unknown product

Removing null and undefined from a type

The NonNullable utility type removes null and undefined from a type. A type guard can remove them from values, for example when filtering an array.

non_nullable.ts
type MaybeName = string | null | undefined;
type Name = NonNullable<MaybeName>;

function isPresent<T>(value: T): value is NonNullable<T> {
    return value != null;
}

const raw: MaybeName[] = ["Alice", null, "Bob", undefined, "Carol"];
const names: Name[] = raw.filter(isPresent);

console.log(names);

The Name type is string. Because isPresent is a type guard, filter returns a string[]. Since TypeScript 5.5, the compiler can also infer this from an inline callback such as x => x != null.

$ npx tsx non_nullable.ts
[ 'Alice', 'Bob', 'Carol' ]

Best Practices

Source

TypeScript Handbook: null and undefined

This tutorial covered null and undefined in TypeScript. With strictNullChecks, the compiler makes sure that missing values are handled before they are used.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.