TypeScript tuples, readonly, and optional chaining
Return fixed-shape pairs, protect arrays from change, and read nested optional values safely.
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
Explanation
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.
Explore the dry run
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 |
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.