Not every property is always there
Real data has gaps: a profile might not have a bio yet. Mark a property optional with ?:
interface Profile { readonly id: number; username: string; bio?: string; }
bio?: string means the property is either a string or absent. TypeScript then forces you to handle the absent case: p.bio.length is an error until you check, for example if (p.bio !== undefined). That check is your first taste of narrowing, which Unit 4 covers properly.
readonly id means the property can be set when the object is created and never reassigned afterwards. p.id = 99 is a compile error. Like const for a single property, and like everything else in TypeScript, it is enforced only at compile time.
Reading an optional property safely
The first call below omits bio entirely, which is legal because bio is optional.
interface Profile { readonly id: number; username: string; bio?: string; } function show(p: Profile): void { if (p.bio !== undefined) { console.log(p.username + ": " + p.bio); } else { console.log(p.username + ": no bio yet"); } } show({ id: 1, username: "ada" }); show({ id: 2, username: "grace", bio: "compiler pioneer" });
Output
ada: no bio yet grace: compiler pioneer
Two annotations worth pausing on
- The
voidreturn type marks a function that returns nothing useful.showprints instead of producing a value, and lesson 5-3 goes deeper on this. - Adding
p.id = 99insideshowis a compile error, becauseidis declaredreadonly. The property can be set when the object is created and never reassigned afterwards.
The real type behind bio?: string
With bio?: string, reading p.bio gives you the type string | undefined.
Optional means the value might not be there, and TypeScript is explicit about that rather than pretending the property is always a string. The union spells out both possibilities.
This is why the compiler blocks p.bio.length outright. On the undefined half of that union there is no .length to read, and calling it would be the classic "cannot read properties of undefined" crash. A check such as if (p.bio !== undefined) rules the undefined case out, and inside that branch the type collapses to plain string so the access becomes legal.
A one-line if: the conditional operator
The next example needs a value that depends on a condition: the mark is "[x]" when a task is done, and "[ ]" otherwise. JavaScript has an expression form of if/else built for exactly this, the conditional operator, often called the ternary operator because it takes three parts. It has not appeared in your courses so far, so here is the whole rule:
const mark = t.done ? "[x]" : "[ ]";
Read condition ? a : b as: evaluate the condition, produce a if it is true, otherwise produce b.
It exists because an if/else statement cannot sit on the right side of an =. The conditional operator is an expression, so it produces a value you can assign, return, or pass as an argument. TypeScript checks both arms like any other expression, so a typo in either branch is still caught.
report: an optional field and a conditional mark
Task has a required title and done flag plus an optional due date, and report renders one line for any task.
interface Task { title: string; done: boolean; due?: string; } function report(t: Task): string { const mark = t.done ? "[x]" : "[ ]"; if (t.due !== undefined) { return mark + " " + t.title + " (due " + t.due + ")"; } return mark + " " + t.title; } console.log(report({ title: "Write resume", done: true })); console.log(report({ title: "Apply to jobs", done: false, due: "Friday" }));
Output
[x] Write resume
[ ] Apply to jobs (due Friday)Three pieces working together
- The optional syntax is
due?: stringinside the interface, which is what makes the first call legal despite having nodue. - The conditional operator from this lesson builds the checkbox:
const mark = t.done ? "[x]" : "[ ]";. Both arms are strings, somarkis inferred asstring. - The
if (t.due !== undefined)guard comes before any use oft.due. Inside it the type isstring, so concatenating it is allowed. Without the guard, the compiler would reject the line even though the code would often work at runtime.