Readonly Class Properties in TypeScript
The readonly modifier prevents reassignment to a class property after the constructor finishes. Learn how to use it, when it helps, and how it differs from const.
A readonly class property can be assigned exactly once: in the field declaration or in the constructor. After the constructor finishes, TypeScript blocks any further assignment.
class Greeter {
readonly name: string;
constructor(name: string) {
this.name = name;
}
rename(newName: string) {
this.name = newName;
}
}TypeScript catches this assignment attempt and produces the following compile error. The readonly modifier prevents any write to the property outside of the constructor body.
Cannot assign to 'name' because it is a read-only property.The constructor assignment is fine. The attempt inside rename is not. TypeScript catches it at compile time and refuses to produce JavaScript for the broken code.
Where Readonly Applies
The constructor is the only place you can assign to a readonly field after its declaration. You can assign as many times as you want inside the constructor:
class User {
readonly id: number;
constructor(rawId: string | number) {
if (typeof rawId === "string") {
this.id = parseInt(rawId, 10);
} else {
this.id = rawId;
}
}
}Both branches of the if-statement assign to this.id. TypeScript accepts this because the constructor is the one place where readonly fields can be set dynamically. This is useful when the field value depends on conditional logic during setup.
After the constructor returns, the field is locked:
const user = new User("42");
user.id = 99;TypeScript reports this error at compile time, since the constructor already finished running and the id field is now permanently locked against reassignment:
Cannot assign to 'id' because it is a read-only property.External code cannot change the id after the object is constructed.
Readonly with an Initializer
You can initialize a readonly field directly at the declaration site. When you do, the initializer is the one and only assignment - even the constructor cannot change it:
class Config {
readonly apiVersion = "v2";
readonly maxRetries = 3;
}
const config = new Config();
console.log(config.apiVersion, config.maxRetries);This prints v2 3, the two values set directly on the field declarations. No constructor is needed at all here.
v2 3A readonly field with an initializer is fully locked. No constructor body or method can reassign it.
Readonly vs Const
These two keywords solve similar problems in different places:
| const | readonly | |
|---|---|---|
| Where used | Variable declarations | Class properties, interface members |
| Scope | Block-scoped | Instance-scoped |
| Runtime behavior | Cannot be reassigned (JS enforces) | Compile-time check only (erased) |
| Can use with objects | Yes, but object is still mutable | Yes, but object is still mutable |
Use const for module-level constants. Use readonly for per-instance values that should not change after construction.
Readonly Is Shallow
The readonly modifier only blocks reassignment of the property reference. If the value is an object or array, its contents can still change:
class Team {
readonly members: string[] = ["Alice", "Bob"];
addMember(name: string) {
this.members.push(name);
}
replaceTeam(newMembers: string[]) {
this.members = newMembers;
}
}The push call works because it mutates the existing array without changing the reference stored in members. The replaceTeam method fails because it tries to assign a new array to members:
Cannot assign to 'members' because it is a read-only property.For a truly immutable array, use the ReadonlyArray utility type instead of a plain array type. This blocks mutations to the array contents as well as reassignment of the property itself.
Readonly with Parameter Properties
Combine readonly with parameter properties to create a field that is set once through the constructor and never changes:
class Point {
constructor(
public readonly x: number,
public readonly y: number
) {}
}
const p = new Point(10, 20);
console.log(p.x, p.y);This prints 10 20, since the public readonly prefix declares and assigns both fields in one step. Now try to change one of the fields after construction:
p.x = 30;TypeScript rejects this on its own, separate from the working example above, because the field was declared readonly. The compiler blocks the assignment with the following error and never emits JavaScript for this line:
Cannot assign to 'x' because it is a read-only property.This pattern is very common for value objects, data transfer objects, and any class where the identity should be fixed at creation time. The public readonly combination gives you a field that anyone can read but no one can change.
When to Use Readonly
Use readonly for fields that should not change after the object is created:
- Identity fields like id, uuid, or createdAt.
- Injected dependencies that are set once by a framework or factory.
- Configuration values that are fixed per instance.
- Derived values that are computed in the constructor and cached.
Do not use readonly for fields that genuinely need to change over the object's lifetime, temporary working state, or counters. Also, a getter with no setter is automatically readonly, so you do not need the keyword there.
Common Mistakes
Expecting deep immutability. Readonly only blocks this.field = newValue. It does not block this.field.push(item) or this.field.nested = value. If you need deep immutability, combine readonly with Readonly utility types or as const.
Trying to use const on a class property. Const only works on variables in block or module scope. Inside a class body, only readonly is available.
Assigning a readonly field in a method called from the constructor. TypeScript does not track assignments through method calls during construction. Even if an init method assigns the field, TypeScript will still report that the field is not definitely assigned. Assign readonly fields directly in the constructor body.
For more on how classes control access, see public private and protected in TypeScript. For enforcing contracts on class shapes, see implements keyword in TypeScript.
Rune AI
Key Insights
- readonly prevents assignment to a class property everywhere except in the constructor.
- It is a compile-time check only. At runtime, readonly properties are plain writable properties.
- readonly is for class properties and interface members. const is for variables.
- readonly is shallow. The value can still be mutated if it is an object or array.
- Combine readonly with access modifiers like public readonly for fields set once via the constructor.
Frequently Asked Questions
Does readonly make a property deeply immutable?
Can I set a readonly property more than once in the constructor?
What is the difference between readonly and const?
Conclusion
The readonly modifier locks a class property after the constructor finishes. It is a compile-time guard that prevents accidental reassignment and signals to other developers that a value should not change. Use it for fields that represent identity, configuration, or injected dependencies. Remember that readonly is shallow: it blocks reassignment of the property but does not freeze the value itself.
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.