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:
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); // undefinedLet's go through the example line by line:
age?: numbermeans the propertyagemay be left out, and if it is there, it holds a number.annhas an age, and the compiler checked that it is a number.bobhas no age, and that is not an error, unlike a missingname.ann.ageprints30, andbob.ageprintsundefined: 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:
type User = { name: string; age?: number };
function printAge(u: User) {
console.log(u.age.toFixed(1)); // ✗
}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:
1type User = { name: string; age?: number };editor hoverage: number | undefined2 3function printAge(u: User) {4 const value = u.age;5 console.log(u.age.toFixed(1));6 if (u.age !== undefined) {7 console.log(u.age.toFixed(1));8 }9 const shown = u.age ?? 0;10 console.log(shown);11}
The ? added undefined to the property's type.
Check first, then use
There are two ways to turn number | undefined into number.
A check with if
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 ageLet's see what the compiler sees:
- In the condition
u.age !== undefined,u.agehas the typenumber | undefined. - Inside the
if, theundefinedmember is ruled out andu.ageis anumber, sotoFixedworks. - After the
if, the function returns a fallback text for those who have no age. - The program prints
30.0andno 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:
type User = { name: string; age: number | undefined };
const bob: User = { name: "Bob" }; // ✗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?: numberis 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: errorTS18048. - Inside
if (u.age !== undefined),u.agehas the typenumber, andu.age ?? 0gives anumberat once. age: number | undefinedis a required property, not an optional one.