TypeScript tuples, readonly, and optional chaining
Return fixed-shape pairs, protect arrays from change, and read nested optional values safely.
On this page
TypeScript arrays and interfaces.
- Return fixed-shape pairs, protect arrays from change, and read nested optional values safely.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A tuple type such as [string, number] describes an array with a fixed length and a known type at each position. It is handy for returning two values from a function and unpacking them with destructuring. A readonly array type, written readonly number[], forbids push, pop, and element assignment, which documents and enforces that a function will not change its input.
Optional chaining a?.b?.c stops and returns undefined if any link is null or undefined, instead of throwing. The nullish coalescing operator ?? supplies a default only when the left side is null or undefined, unlike ||, which also replaces 0 and the empty string. Together they make code that handles missing data short and safe.
Code example
function minMax(values: readonly number[]): [number, number] {
return [Math.min(...values), Math.max(...values)];
}
const [low, high] = minMax([4, 9, 2, 7]);
console.log(low, high);
interface Address {
city?: string;
}
interface Person {
name: string;
address?: Address;
nickname?: string;
visits: number;
}
const people: Person[] = [
{ name: "Ada", address: { city: "London" }, nickname: "A", visits: 0 },
{ name: "Bo", visits: 3 },
];
for (const person of people) {
const city = person.address?.city ?? "unknown";
const label = person.nickname ?? person.name;
const visits = person.visits || 10;
const visitsKept = person.visits ?? 10;
console.log(label + " / " + city + " / " + visits + " / " + visitsKept);
}2 9 A / London / 10 / 0 Bo / unknown / 3 / 3
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
function minMax(values: readonly number[]): [number, number] { return [Math.min(...values), Math.max(...values)];} const [low, high] = minMax([4, 9, 2, 7]);console.log(low, high); interface Address { city?: string;}interface Person { name: string; address?: Address; nickname?: string; visits: number;} const people: Person[] = [ { name: "Ada", address: { city: "London" }, nickname: "A", visits: 0 }, { name: "Bo", visits: 3 },]; for (const person of people) { const city = person.address?.city ?? "unknown"; const label = person.nickname ?? person.name; const visits = person.visits || 10; const visitsKept = person.visits ?? 10; console.log(label + " / " + city + " / " + visits + " / " + visitsKept);}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| The input is readonly, so the function cannot modify it, and the result is a pair of numbers. | — | — | |
| The tuple is unpacked into two variables. | — | — | |
| The smallest and largest values. | — | 2 9 | |
| For Bo, address is undefined, so the whole chain gives undefined and ?? supplies the default. | — | 2 9 | |
| || treats 0 as missing, so Ada's zero visits become 10. | — | 2 9 | |
| ?? replaces only null and undefined, so 0 is kept. | — | 2 9 | |
| Ada shows the difference between || and ??. Bo has no address, so the city is unknown. | — | 2 9 A / London / 10 / 0 Bo / unknown / 3 / 3 |
Behind the scenes
How TypeScript processes this code
TypeScript checks types and emits JavaScript. Type annotations are removed before execution.
Cost and common mistakes
minMax is O(n); the property accesses are O(1).
- Math.min(...values) with a very large array can exceed the argument limit. Use a loop for huge arrays.
- readonly is a compile-time promise only. It does not stop code that casts it away, and it does not freeze the array at run time.
- ?. only guards against null and undefined. A value of the wrong type still causes an error.
Try it yourself
What does person.address?.city return when address exists but city is missing?
Reveal answer and reasoning
undefined, because the city property is optional and not set; ?? "unknown" then supplies the default.