Q10 of 38 · TypeScript
How does the `readonly` modifier work in TypeScript, and what are its limitations?
Short answer
Short answer: `readonly` prevents reassignment of a property after initialization at compile time. It does not deep-freeze nested objects — a `readonly` reference to an object still allows mutating that object's properties. It is a compile-time constraint only; the emitted JavaScript contains no runtime enforcement.
Detail
readonly is a TypeScript-only compile-time annotation that prevents reassignment of a property or array element.
On interface/type properties: readonly id: number means the id field cannot be assigned after object creation.
On class properties: readonly class properties can be set in the constructor but nowhere else. TypeScript enforces this at compile time.
Readonly<T> utility type: Wraps an entire type making all properties readonly.
readonly arrays: readonly string[] (or ReadonlyArray<string>) prevents push/pop/splice and reassignment of elements. The as const assertion creates a deeply readonly tuple/literal.
Shallow limitation: Like Object.freeze(), readonly is shallow. A readonly property that holds an object reference does not prevent mutation of that object's own properties — only reassignment of the reference itself.
No runtime impact: The compiled JavaScript output has no enforcement. A non-typed caller or a type assertion (as any) can still mutate readonly properties. Use Object.freeze() for runtime enforcement.
// EXAMPLE
interface User {
readonly id: number;
name: string;
}
const user: User = { id: 1, name: "Alice" };
user.name = "Bob"; // ok — name is not readonly
// user.id = 2; // Error: Cannot assign to 'id' (readonly)
// Readonly utility type
type ImmutableUser = Readonly<User>;
// Class readonly
class Config {
readonly baseUrl: string;
constructor(url: string) {
this.baseUrl = url; // ok in constructor
}
setUrl(url: string) {
// this.baseUrl = url; // Error outside constructor
}
}
// Shallow — nested object is still mutable!
interface Team {
readonly members: string[];
}
const team: Team = { members: ["Alice"] };
// team.members = []; // Error: cannot reassign
team.members.push("Bob"); // OK — push mutates the array, not the reference
// Deep readonly with as const
const LEVELS = ["junior", "mid", "senior"] as const; // ReadonlyArray