C.W.K.
Stream
Lesson 03 of 05 · published

`readonly` Arrays and Tuples

~8 min · arrays-tuples, readonly, immutability

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"A readonly array is the array's type system saying 'you can read, you can't write.' The runtime still lets you — but the compiler will yell."

readonly array shapes

Two equivalent syntaxes mark an array as read-only at the type level: readonly T[] (modifier) and ReadonlyArray<T> (utility-type alias). Both produce the same type — an array whose mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill) are removed from the type. Reading (length, index access, map, filter, slice, etc.) is still allowed.

At runtime, the array is a regular array. The readonly modifier exists only in the type layer; the JavaScript engine doesn't enforce it. This is consistent with the rest of TypeScript: compile-time contract, runtime erasure.

readonly tuples

Tuples accept the same modifier: readonly [number, string]. The whole tuple — both length and per-position writability — is frozen. The bonus: as const on a tuple literal automatically produces a readonly tuple, which is one of the reasons that idiom is so common.

Why annotate function parameters as readonly

If your function doesn't intend to mutate its array argument, type the parameter as readonly T[]. Two benefits: the compiler enforces inside the function that you don't accidentally mutate, AND the callsite knows you won't change their data. The combination is a one-word safety improvement that costs nothing.

function sumAll(items: readonly number[]): number {
  // items.push(0)   // ❌ caught inside
  return items.reduce((acc, n) => acc + n, 0);
}
The asymmetry to know: a regular array IS assignable to a readonly array (because removing mutation methods is widening). A readonly array is NOT assignable to a regular array (because adding mutation methods would be promising more than you have). Once data has crossed into readonly territory, it can't go back without a copy.

Pippa's confession

cwkPippa's frontend tries to default to readonly T[] on function parameters. The conversion cost from one mindset to the other is one keyword; the benefit is dozens of tiny "don't accidentally mutate" guarantees that compound. The exceptions — where I want the function to mutate — are rare enough to be worth marking with a comment.

Code

readonly modifier — what's allowed, what isn't·typescript
// readonly modifier on arrays.
const frozen: readonly number[] = [1, 2, 3];
frozen[0] = 99;       // ❌ Index signature in type 'readonly number[]' only permits reading
frozen.push(4);       // ❌ Property 'push' does not exist on type 'readonly number[]'

// Read operations still allowed.
const doubled = frozen.map((n) => n * 2);       // ✅
const length = frozen.length;                    // ✅

// readonly tuple.
type Pair = readonly [number, number];
const p: Pair = [1, 2];
p[0] = 99;            // ❌ readonly

// readonly is a one-way relationship.
const mutable: number[] = [1, 2, 3];
const alsoFrozen: readonly number[] = mutable;   // ✅ widens to readonly
// const backToMutable: number[] = alsoFrozen;    // ❌ can't drop readonly without copy
const copy: number[] = [...alsoFrozen];          // ✅ explicit copy
readonly parameters — small word, big invariant·typescript
// Function parameters — defaults you should adopt.

function summarize(items: readonly string[]): string {
  // items.sort()   // ❌ would mutate — caught
  return items.join(', ');
}

// If you need to sort, copy first.
function summarizeSorted(items: readonly string[]): string {
  return [...items].sort().join(', ');
}

// The signal to the caller: 'I won't touch your data.'
const raw = ['c', 'a', 'b'];
summarize(raw);          // ✅ — raw stays intact
summarizeSorted(raw);    // ✅ — raw stays intact

External links

Exercise

Take a function that takes a User[] and returns a list of emails. Add readonly to the parameter. Does anything inside the function break? If yes, adjust (probably to [...users] for any sort) and observe how the readonly version is now safer for callers.
Hint
Most read-only chains (map, filter, reduce) survive. Sort, splice, and any direct index assignment will fail. The fix is almost always 'copy first.'

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.