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.
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.
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.
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.
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.
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.
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.
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.
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.
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
- Enable strictNullChecks: Use
"strict": trueso that missing values are tracked by the compiler. - Prefer ?? to ||: Use nullish coalescing for default values, so that
0,false, and""are kept. - Check with == null: One comparison covers both
nullandundefined. - Avoid the ! Operator: Narrow the value or provide a default instead of asserting that it exists.
- Pick One Empty Value: Use
undefined(optional properties) consistently, andnullonly where an API requires it.
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
List all TypeScript tutorials.