Read-only arrays
A left-luggage office may also have a display case: a row of lockers with glass doors under lock. A visitor sees what lies in each one, can count the items and read the numbers. But they cannot open a door, put an item in or take one out.
An ordinary array is like open lockers. A function that receives an array can change it, and the calling code will not know. Let's look at a function that finds the best score:
function best(list: number[]) {
list.sort((a, b) => b - a);
return list[0];
}
const scores = [70, 95, 80];
console.log(best(scores)); // 95
console.log(scores.join(" ")); // 95 80 70Let's go through the example line by line:
bestsorts the array in descending order and returns the first element.- The
sortmethod makes no copy: it rearranges the elements in the same array. best(scores)prints95, as expected.- But
scores.join(" ")prints95 80 70: the function rearranged an array it was given only to look at. aandbin(a, b) => b - ahave no type, yet there is noTS7006error. TypeScript knows thatsorton anumber[]passes two numbers to this function, so it givesaandbthe typenumberitself.
A bug like this is hard to find: the order breaks in one place and is noticed somewhere else entirely.
The readonly number[] type
The word readonly before an array type makes it a read-only array: its elements can be read, but the array itself cannot be changed.
In the previous theme readonly stood before a property. Before a type it is allowed only on arrays and tuples: readonly User gives error TS1354.
const scores: readonly number[] = [70, 95, 80];
console.log(scores[0]);
scores.push(60); // ✗error TS2339: Property 'push' does not exist on type 'readonly number[]'.The message means "the type readonly number[] has no property push". The methods that change an array are absent from this type altogether, which is why tsc says push does not exist.
What a read-only array keeps and what it loses:
- you cannot call
push,pop,sort,reverseorsplice, anything that changes the array itself; - you cannot write by index:
scores[0] = 60gives errorTS2542; - you can read by index, check
lengthand walk the array with afor…ofloop; - you can call the methods that return a new array:
map,filter,slice,toSorted.
Like an ordinary array, this type has a long form: ReadonlyArray<number> is the same type.
A parameter that promises not to change
Most often readonly is written on a function parameter. Such an annotation is a promise in the function's signature: "I will only read your array". Let's rewrite best:
function best(list: readonly number[]) {
return list.toSorted((a, b) => b - a)[0];
}
const scores = [70, 95, 80];
console.log(best(scores)); // 95
console.log(scores.join(" ")); // 70 95 80Let's see what changed:
list: readonly number[]means the array can only be read inside the function.toSortedreturns a sorted copy and leaves the original array alone.- An ordinary array,
scores, was passed tobest, and that is allowed: the function promises to do less with it than the typenumber[]permits. - The second line prints
70 95 80: the order inscoresstayed the same.
If you keep list.sort(...) inside, tsc stops it with error TS2339: readonly number[] has no sort method. That way the promise in the signature is checked on every line of the function.
Only tsc checks it
readonly is part of the type, and types are erased. Nothing of readonly number[] is left in the JavaScript file, and at run time the array is an ordinary one: push works. Only tsc's check before the run protects it.
Here the display-case analogy stops working. A display case is closed to everyone, while readonly closes the array only to whoever looks at it through this type. After const view: readonly number[] = scores;, the call scores.push(60) goes through, since scores has the type number[], and view.length becomes 4.
Interviewers often ask how readonly number[] differs from const:
const scoresforbids putting another array into the namescores, but allowsscores.push(60);readonly number[]forbids changing the array itself, but aletvariable of this type can be given another array.
Together, in const scores: readonly number[], they close both the name and the contents.
Common mistake
A read-only array was passed to a function with an ordinary number[] parameter:
function total(list: number[]) {
let sum = 0;
for (const n of list) {
sum += n;
}
return sum;
}
const scores: readonly number[] = [70, 95, 80];
console.log(total(scores)); // ✗error TS4104: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.tsc answers that the type readonly number[] is read-only and cannot be assigned to the mutable type number[]. A number[] parameter lets the function change the array, and scores must not be changed. The function total does not change the array, so the fix is to give it the same promise: function total(list: readonly number[]).
The rule works one way: an ordinary array fits a readonly number[] parameter, while a read-only array does not fit a number[] parameter.
Key points
readonly number[]is an array you can read but not change:pushgives errorTS2339, a write by indexTS2542.readonlyon a parameter promises not to touch the calling code's array; an ordinary array can be passed to such a parameter.- A read-only array does not fit a
number[]parameter: errorTS4104. constprotects the variable's name,readonlythe array's contents, and only tsc checks it.