TypeScript · Course navigation

TypeScript tuples, readonly, and optional chaining

Return fixed-shape pairs, protect arrays from change, and read nested optional values safely.

On this page
Before you start

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

TypeScript
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);
}
Output
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
Guided walkthroughReady
1function minMax(values: readonly number[]): [number, number] {
2 return [Math.min(...values), Math.max(...values)];
3}
4
5const [low, high] = minMax([4, 9, 2, 7]);
6console.log(low, high);
7
8interface Address {
9 city?: string;
10}
11interface Person {
12 name: string;
13 address?: Address;
14 nickname?: string;
15 visits: number;
16}
17
18const people: Person[] = [
19 { name: "Ada", address: { city: "London" }, nickname: "A", visits: 0 },
20 { name: "Bo", visits: 3 },
21];
22
23for (const person of people) {
24 const city = person.address?.city ?? "unknown";
25 const label = person.nickname ?? person.name;
26 const visits = person.visits || 10;
27 const visitsKept = person.visits ?? 10;
28 console.log(label + " / " + city + " / " + visits + " / " + visitsKept);
29}

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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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.

Further reading