Readonly Values in TypeScript
Readonly values in TypeScript prevent accidental mutation. Learn readonly properties, ReadonlyArray, readonly tuples, the Readonly utility type, and how readonly differs from const.
The readonly modifier prevents a property or array element from being reassigned after it is set. It is a compile-time check only with zero runtime cost. Think of readonly as a property-level version of const: while const prevents reassignment of a variable, readonly prevents reassignment of a property inside an object.
Readonly Properties
Add readonly before a property name in an interface or type alias. The property can be read freely, but any attempt to reassign it produces a compiler error.
interface User {
readonly id: number;
name: string;
}
const user: User = { id: 1, name: "Alice" };
console.log(user.id); // OK
user.name = "Bob"; // OK
user.id = 2;
// Error: Cannot assign to 'id' because it is a read-only propertyThe id property is locked. The name property is writable as usual. This is useful for fields that should be set once and never changed, like database IDs, creation timestamps, or foreign keys.
Readonly is shallow. It protects the property reference, not the value inside.
If the readonly property holds an object, the nested properties remain mutable. For deep immutability, you need as const or recursive readonly types.
Readonly Arrays
TypeScript provides ReadonlyArray for arrays that should not be mutated. The shorthand is readonly followed by the element type in square brackets. All mutating methods like push, pop, and splice are blocked.
function printScores(scores: readonly number[]) {
console.log(scores[0]); // OK
console.log(scores.length); // OK
scores.push(100);
// Error: 'push' does not exist on 'readonly number[]'
}Read-only methods like slice, map, filter, and reduce still work because they return new arrays without modifying the original. You can assign a regular array to a readonly array variable, but not the other way around. This one-way assignability is intentional: it means you can pass a mutable array to any function that expects a readonly array, but a function receiving a readonly array cannot accidentally mutate the caller's data.
Use readonly arrays in function parameters whenever the function reads but does not modify the array. It documents the contract and prevents accidental internal mutations.
Readonly Tuples
Tuples can also be readonly. Prefix the tuple type with the readonly keyword. Arrays created with as const are automatically readonly tuples, which means passing them to functions expecting mutable tuples will fail.
const point = [3, 4] as const;
// Type: readonly [3, 4]
function distance([x, y]: readonly [number, number]) {
return Math.sqrt(x ** 2 + y ** 2);
}
distance(point); // OKIf the parameter were declared as a mutable tuple, the call would fail because readonly tuples are not assignable to mutable ones. Always declare tuple parameters as readonly when the function does not modify them.
The Readonly Utility Type
TypeScript provides a built-in Readonly utility type that makes every property of a given type readonly in one line. You do not need to create a separate immutable interface.
interface Todo {
title: string;
completed: boolean;
}
function markComplete(todo: Readonly<Todo>) {
console.log(todo.title); // OK
todo.completed = true;
// Error: Cannot assign to 'completed' because it is a read-only property
}Use the Readonly utility type when you want to pass an object to a function and guarantee it will not be modified. It works with any object type, including interfaces, type aliases, and inline object types.
Readonly vs Const vs As Const
These three features serve different purposes and are often used together:
| Feature | What it protects | Depth |
|---|---|---|
| const | Variable binding | Shallow |
| readonly | Property or array element | Shallow |
| as const | Entire object or array with literal types | Deep |
You can combine all three. A const variable with as const gives you a deeply immutable object where every property is readonly and every value has a literal type. This single assertion provides maximum type safety with no extra syntax beyond the two keywords.
Common Mistakes
The biggest misunderstanding is expecting readonly to be deep. A readonly object property still contains mutable inner properties. The modifier only protects the property itself, not the object tree below it.
Another common mistake is forgetting that readonly is compile-time only. At runtime, readonly properties behave like any other property. An object can be mutated through aliasing even when the type says readonly.
The modifier prevents accidental mutations in your code, but it cannot stop deliberate mutations through shared references.
For more on how as const works with literal types and immutability, see TypeScript literal types explained. For the difference between const bindings and readonly properties, see Let and const in TypeScript.
Rune AI
Key Insights
- readonly prevents reassignment of a property, not deep immutability.
- ReadonlyArray blocks mutating methods like push and splice at compile time.
- The Readonly utility type makes all properties of a type readonly in one line.
- readonly is compile-time only; it does not exist in the JavaScript output.
- Use readonly in function parameters to prevent accidental mutations.
Frequently Asked Questions
What is the difference between const and readonly?
Does readonly affect runtime behavior?
Can I make a readonly property mutable again?
Conclusion
readonly is a lightweight way to signal that a property or array should not be changed. It catches accidental mutations at compile time with zero runtime cost. Use readonly properties on interfaces that represent data you consume but should not modify. Use ReadonlyArray for function parameters to promise callers you will not mutate their arrays. Pair readonly with const and as const for a complete immutability strategy.
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.