Readonly Arrays in TypeScript
A readonly array prevents accidental mutation by blocking push, pop, sort, and index assignment. Learn the two syntaxes, when to use readonly, and how it differs from const.
TypeScript readonly arrays are arrays that you can read from but cannot change. TypeScript blocks every mutation method, including push, pop, sort, splice, and index assignment, at compile time.
The array still works like a normal array at runtime, but the type system prevents accidental changes that could introduce bugs. This is useful when a function receives an array and should only read from it, or when you return an internal array and want to prevent callers from modifying it.
The readonly modifier is purely a compile-time concept with zero runtime overhead.
Two syntaxes for readonly arrays
TypeScript gives you two equivalent ways to write a readonly array type. The bracket form places the readonly keyword before the element type and brackets, while the generic form uses the ReadonlyArray type from the standard library. Both produce identical types.
function logAll(values: readonly string[]) {
for (let value of values) {
console.log(value);
}
}The ReadonlyArray generic form does the same thing and is interchangeable with the bracket syntax. The bracket form is more common in everyday code because it matches the pattern used for regular typed arrays.
function logAll(values: ReadonlyArray<string>) {
for (let value of values) {
console.log(value);
}
}Choose whichever form your team prefers and stay consistent. Both communicate the same intent: this function reads the array but promises not to change it.
TypeScript's own standard library types use the bracket form for this pattern. The array parameter passed to a map or forEach callback is typed as readonly, so recognizing both syntaxes is useful even if you write mostly with one form.
What readonly blocks
Every method that changes the array in place is a compiler error on a readonly array. This includes all the mutation methods you would find on a regular array.
let tags: readonly string[] = ["typescript", "tutorial"];
tags.push("javascript");
tags.pop();
tags.sort();
tags[0] = "rust";
tags.splice(1, 1);The compiler rejects every one of these calls because the methods do not exist on the readonly array type. Instead of a vague error, you get a precise message that names the method and explains why it is unavailable.
Property 'push' does not exist on type 'readonly string[]'.
Property 'pop' does not exist on type 'readonly string[]'.Every method that reads and returns a new value still works perfectly. Methods like map, filter, slice, find, and forEach do not change the original array, so they satisfy the readonly constraint.
let tags: readonly string[] = ["typescript", "tutorial"];
let first = tags[0];
let copy = tags.slice();
let filtered = tags.filter(t => t.length > 5);
let mapped = tags.map(t => t.toUpperCase());All four operations succeed because none of them mutate the original array. The readonly type only restricts writes, never reads.
Assignability rules
A mutable array is always assignable to a readonly array. The readonly type makes a weaker promise -- "I will not change this" -- and it is always safe to make a weaker promise about data you control.
let mutable: string[] = ["a", "b"];
let readOnly: readonly string[] = mutable;This assignment succeeds without any type assertion. TypeScript trusts that using the array through the readonly reference is safe.
The reverse is not allowed. A readonly array cannot be assigned to a mutable array reference because the mutable reference could be used to push or pop, breaking the readonly guarantee on the original data.
let readOnly: readonly string[] = ["a", "b"];
let mutable: string[] = readOnly;The compiler blocks this assignment. This one-way assignability is intentional: it means you can pass any array to a function that takes a readonly parameter, but the function cannot accidentally hand the readonly array off to code that would mutate it.
Where readonly arrays shine
Function parameters benefit the most from readonly annotations. When a function only reads from an array, declaring the parameter as readonly signals intent and lets callers pass either kind of array.
function displayScores(scores: readonly number[]) {
let highest = Math.max(...scores);
console.log(`Highest score: ${highest}`);
}
let examScores: number[] = [88, 92, 79];
displayScores(examScores);The function only reads from scores, so a plain mutable array works fine here. The next example passes a readonly array to the same function.
let readonlyScores: readonly number[] = [95, 87, 91];
displayScores(readonlyScores);Both calls work because the function only reads from the array. It does not care whether the caller's array is mutable or readonly.
Return values also benefit from readonly. When you expose an internal array, use readonly to prevent callers from mutating it. This is a compile-time-only pattern: at runtime, the array is still mutable, but the type annotation communicates "do not modify this."
Readonly array vs const
The const keyword and the readonly modifier do different things and can be combined. A quick comparison helps clarify when to use each:
| Feature | const | readonly array |
|---|---|---|
| Prevents reassignment of the variable | Yes | No |
| Prevents push, pop, sort, splice | No | Yes |
| Prevents index assignment | No | Yes |
| Runtime cost | None (JS const) | None (compile-time only) |
Use const when you want to prevent reassigning the variable to a different array. Use readonly when you want to prevent changing the array contents.
Use both together when you want a fixed array reference with fixed contents. For more on compile-time immutability, see readonly values in TypeScript.
Common mistakes
A readonly array only protects the top-level structure. If each element is an object, the objects themselves remain fully mutable, because the readonly modifier does not recursively make nested objects immutable.
let users: readonly { name: string }[] = [{ name: "Ada" }];
users[0].name = "Grace";This assignment compiles without error. The readonly modifier blocks users.push() and users[0] = ..., but it never looks inside the objects the array holds.
Another common mistake is expecting the ReadonlyArray type to have a constructor. There is no ReadonlyArray constructor because ReadonlyArray is a type, not a value.
Create a regular array and annotate it as readonly, or use a const assertion for literal arrays where the exact values should not change. For more on const assertions, see use as const in TypeScript.
Rune AI
Key Insights
- readonly T[] and ReadonlyArray<T> both create an array type that blocks mutation methods.
- Methods like push, pop, sort, splice, and index assignment are all compiler errors on a readonly array.
- Read methods like map, filter, slice, find, and forEach still work perfectly.
- A mutable array is assignable to readonly, but not the reverse.
- Use readonly arrays on function parameters to signal that the function only reads from the array.
Frequently Asked Questions
What is the difference between const and readonly arrays?
Can I assign a regular array to a readonly array?
Can I assign a readonly array to a regular array?
Conclusion
Readonly arrays are a lightweight way to prevent accidental mutation without adding runtime cost. Annotate function parameters as readonly when the function only reads from the array, and use readonly return types to signal that callers should not modify the result. Every mutation method is blocked at compile time, so the guarantee costs nothing at runtime.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.