In TypeScript, what does marking a class property `readonly` guarantee, and how is that different from declaring a variable with `const`?
answer
- one restricts a property, one a binding
- two legal write sites
- declaration or constructor only
- shallow — contents stay mutable
- erased at compile time
basics
~20 sreadonly is a compile-time-only restriction on writing a property: assignment is allowed just in the property's declaration or the declaring class's constructor. const restricts rebinding a variable instead. Neither stops mutation of the object inside.
solid answer
~50 s`readonly` is a TypeScript modifier on a **property**. The checker permits an assignment to it in exactly two places: the property's own initializer, and the constructor of the class that declares it. Anywhere else — a method, a subclass constructor, or code outside the class — you get "Cannot assign to 'x' because it is a read-only property". `const` is a JavaScript **declaration form** for a variable binding: it says the binding can never be re-pointed, and it says nothing about properties. The two also differ in lifetime: `const` survives into the emitted JavaScript and is enforced by the engine, while `readonly` is erased with the rest of the type layer, so the compiled output has no check at all. Both are shallow — `readonly items: string[]` still lets you `push` into the array, just as `const obj = {}` still lets you assign `obj.a`.
go deeper
Be able to say plainly that readonly goes on a property and const goes on a variable, and that a readonly field can be set in its declaration or the constructor and nowhere else.
Explain that readonly is erased at compile time while const is real JavaScript the engine enforces, and show that both are shallow by mutating the contents of a readonly array field.
Show where the guarantee stops mattering in production code — untyped consumers, assertion boundaries, aliasing through a mutable-typed variable — and say when you would spend a runtime mechanism instead.
Own the convention: decide whether the codebase treats readonly as the default for constructor-set fields, what that buys in review, and where a team pays for confusing a checked promise with an enforced one.
## Two different things are being restricted The confusion between `const` and `readonly` disappears once you see that they constrain different things. `const` is a JavaScript declaration form. It creates a *binding* — a name — and forbids that name from ever being pointed at a different value. It has nothing to say about the value itself. `readonly` is a TypeScript-only modifier that goes on a *property* of a class (or of an object type). It tells the type checker to reject writes to that property outside a small allowed window. It cannot be applied to a variable declaration, and `const` cannot be applied to a class member — they are not two spellings of one idea, they are two different tools. ```ts const limit = 10; limit = 11; // Error: Cannot assign to 'limit' because it is a constant class Config { readonly url: string; constructor(url: string) { this.url = url; } } ``` ## Where the compiler allows a write A `readonly` property may be assigned in exactly two positions: 1. Its own declaration initializer. 2. The body of the constructor of the class that declares it. Everything else is an error, including a method on the same class and, importantly, the constructor of a subclass — the privilege belongs to the declaring class, not to the inheritance chain. ```ts class Account { readonly id: string; readonly createdAt = new Date(); // allowed: declaration initializer constructor(id: string) { this.id = id; // allowed: declaring class's constructor } rename(id: string) { this.id = id; // Error: read-only property } } ``` That constructor window is the whole point of the feature: it lets you model a value that is decided once, at construction, and never again. It is the type-level way of saying "this is part of the object's identity". ## Both are shallow Neither modifier reaches inside the value. ```ts class Basket { readonly items: string[] = []; } const b = new Basket(); b.items.push("apple"); // fine — the array itself is not readonly b.items = []; // Error — the property binding is ``` The same is true of `const`: `const cfg = { retries: 3 }` happily accepts `cfg.retries = 5`. If you want the elements protected too, you have to say so in the element type (a `readonly` array type, or a mapped-over type) — the modifier on the outer property does not propagate. ## readonly is erased; const is not This is the point interviewers usually push on. TypeScript emits JavaScript with the type layer removed, and `readonly` is part of that layer. The compiled class has an ordinary writable property; nothing in the runtime object stops a write. `const`, by contrast, is real JavaScript syntax that the engine enforces — reassigning a `const` throws a `TypeError` at run time. So `readonly` protects *your* code while it is being checked. It does not protect an object handed to untyped JavaScript, to a consumer who used a type assertion, or to anything crossing an `any` boundary. ## static readonly `static readonly` marks a class-level constant: it lives on the constructor object rather than on instances, and it may be assigned in its initializer or in a `static` initialization block. It is a common way to give a constant a namespace — `Http.DEFAULT_TIMEOUT` — where a module-level `const` would be a bare exported name. The `readonly` half is still erased; only the `static` member itself survives into the emitted JavaScript. ## Choosing between them Use `const` for every local and module-level binding by default — it is free and enforced. Use `readonly` on class fields that are set at construction and should never change afterwards: identifiers, injected collaborators, configuration snapshots. It costs nothing at run time and turns a class of bugs into compile errors, which is exactly the trade the type layer exists to make. What it does *not* buy you is immutability. If a caller must be unable to mutate the value even from untyped code, that is a runtime concern and needs a runtime mechanism — a private field with only a getter, or freezing the object — not a type modifier.
- Can a subclass constructor assign to a `readonly` property declared on its base class?No. The write privilege belongs to the class that declares the property, so a derived constructor assigning `this.id` gets the read-only error even though the field is inherited and visible. If a subclass needs to decide the value, it passes it up through `super(...)` and lets the base constructor do the assignment.
- If `readonly` is erased, can another piece of typed code still write the property?Yes. TypeScript ignores `readonly` differences when checking assignability between object types, so you can assign the instance to a variable typed with an identical but mutable shape and write through that alias with no error. It is a known, deliberate hole in the checker rather than a bug.
- When would you reach for `static readonly` instead of an exported module-level `const`?When the constant is conceptually part of the class's public surface and reads better qualified — `Retry.MAX_ATTEMPTS` rather than a loose imported name. Mechanically the module `const` is stronger, since the engine enforces it; `static readonly` only enforces at check time, but it groups related constants with the type that owns them.
saying these in an interview costs you the question
- Says readonly makes the object immutable at run time
- Treats readonly and const as interchangeable spellings
- Claims readonly compiles to Object.freeze
- Thinks readonly can be assigned in any method of the class
- Believes readonly on an array property blocks push