Skip to content
Theory · Object types · lesson 4 of 7

Optional properties

Some forms mark a field "optional": "Age (optional)". Such a field may stay empty, and the form is still accepted. But whoever reads the form later cannot take the age and compute something with it right away: first they look whether the field was filled in.

In TypeScript such a field is an optional property. It is marked with ? after its name:

TypeScript
type User = {
    name: string;
    age?: number;
};

const ann: User = { name: "Ann", age: 30 };
const bob: User = { name: "Bob" };

console.log(ann.age); // 30
console.log(bob.age); // undefined

Let's go through the example line by line:

  1. age?: number means the property age may be left out, and if it is there, it holds a number.
  2. ann has an age, and the compiler checked that it is a number.
  3. bob has no age, and that is not an error, unlike a missing name.
  4. ann.age prints 30, and bob.age prints undefined: that is how JavaScript answers a read of a property the object does not have.

What a ? property holds

Reading bob.age, the program gets undefined. That is why TypeScript treats an optional property as holding not a number but the union number | undefined: a number may come, or nothing at all.

The usual rule of unions applies to it: only what suits both members is allowed. undefined has no methods, so a number's method cannot be called right away:

TypeScript
type User = { name: string; age?: number };

function printAge(u: User) {
    console.log(u.age.toFixed(1)); // ✗
}
TEXT
error TS18048: 'u.age' is possibly 'undefined'.

The compiler says "u.age may turn out to be undefined". And it is right: if this JavaScript ran for a user without an age, the program would crash on this line with TypeError: Cannot read properties of undefined (reading 'toFixed').

Arithmetic fails the same way: u.age + 1 gets the same TS18048, although JavaScript would quietly give NaN.

Here the form analogy stops working. An empty field on paper is visible at once, while the function does not know which object it will get: that is decided only when the program runs. So the check is written in the code, and after it the type changes. Step through the function and watch the type of u.age before the check and after it:

LiveThe type of an optional propertystep 1 of 6

The ? added undefined to the property's type.

Press "Next step": the hover shows a type or an error on the highlighted line

Check first, then use

There are two ways to turn number | undefined into number.

A check with if

TypeScript
type User = { name: string; age?: number };

function ageText(u: User): string {
    if (u.age !== undefined) {
        return u.age.toFixed(1);
    }
    return "no age";
}

console.log(ageText({ name: "Ann", age: 30 })); // 30.0
console.log(ageText({ name: "Bob" })); // no age

Let's see what the compiler sees:

  1. In the condition u.age !== undefined, u.age has the type number | undefined.
  2. Inside the if, the undefined member is ruled out and u.age is a number, so toFixed works.
  3. After the if, the function returns a fallback text for those who have no age.
  4. The program prints 30.0 and no age.

A check with if fits when an empty field needs its own branch: a different text or a different action.

A default with ??

The ?? operator takes the value on its right when the left side is null or undefined. In const shown = u.age ?? 0;, shown has the type number: 0 takes the place of undefined, and nothing is left to check.

This way fits when an empty field has a sensible default. But the fallback must not mislead: an age of 0 in a report looks real, and then a separate branch with if is better.

Common mistake

An optional property was written as a union:

TypeScript
type User = { name: string; age: number | undefined };

const bob: User = { name: "Bob" }; // ✗
TEXT
error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'User'.

age: number | undefined means something else: the property is required, although its value may be undefined. So a literal without age fails, and you would have to write { name: "Bob", age: undefined }. If the field may be left out, it needs the ?: age?: number. Interviewers like to ask about the difference between these two forms.

Key points

  • age?: number is a property that may be left out of the object.
  • It reads as number | undefined, and without a check you can neither call a number's method on it nor do arithmetic with it: error TS18048.
  • Inside if (u.age !== undefined), u.age has the type number, and u.age ?? 0 gives a number at once.
  • age: number | undefined is a required property, not an optional one.