Skip to content
Theory · Object types · lesson 6 of 7

Read-only properties

Not every field of a form is yours to fill in. The HR office prints the form's number in advance, at the print shop. The other fields are written in pen, while the printed number cannot be corrected: it is how the form is found in the archive.

In JavaScript every property of an object is like a field for a pen: it can be overwritten. Sometimes that is dangerous. If a user's id changes, every record that refers to that id will point at someone else.

The readonly modifier before a property makes it a read-only property: you can read it, but not write it once the object is created.

TypeScript
type User = {
    readonly id: number;
    name: string;
};

const user: User = { id: 1, name: "Ann" };
user.name = "Anna";
console.log(user.id, user.name); // 1 Anna

Let's go through the example line by line:

  1. readonly id: number makes the property id read-only.
  2. The literal { id: 1, name: "Ann" } sets id when the object is created, and that is allowed: the number is printed once, when the blank is issued.
  3. user.name = "Anna" changes an ordinary property, and the compiler does not object.
  4. The program prints 1 Anna.

Mark as readonly whatever must not change once the object is created: ids, creation dates, initial settings.

Writing to a readonly property

Let's try to change the number after the object is created:

TypeScript
type User = {
    readonly id: number;
    name: string;
};

const user: User = { id: 1, name: "Ann" };
user.id = 2; // ✗
TEXT
error TS2540: Cannot assign to 'id' because it is a read-only property.

The compiler answers that id cannot be assigned because it is a read-only property. The error sits on the line with the write, not in the type declaration: the compiler points at the place where the rule was broken.

readonly and const protect different things:

  • const user forbids putting another object into the variable, but does not stop the object's properties from changing;
  • readonly id protects one property, whatever variable holds the object, a const or a let.

Only tsc checks it

readonly is part of the type, and types are erased. The compiler tsc reports the error in the example above, but it still writes the JavaScript file, and the word readonly is not in it. If that JavaScript runs, the assignment user.id = 2 works, and id becomes 2. The same happens when you run the file with node file.ts: it erases the types without checking them.

Here the analogy with the printed field stops working. Print-shop ink cannot be rubbed off, while readonly works only as long as the compiler checks the code.

Interviewers like to ask how readonly differs from Object.freeze. The answer: tsc checks readonly before the program runs, while the JavaScript function Object.freeze forbids writes to an object while the program runs.

One level of protection

A property may hold another object, like an address insert inside the form. Let's check whether readonly protects its fields:

TypeScript
type User = {
    readonly id: number;
    readonly address: { city: string };
};

const user: User = { id: 1, address: { city: "Oslo" } };
user.address.city = "Rome";
console.log(user.address.city); // Rome

Let's see what is protected here and what is not:

  1. readonly address forbids putting another object into address: the compiler rejects user.address = { city: "Rome" } with the same error, TS2540.
  2. The property city inside the address is an ordinary one, so user.address.city = "Rome" passes.
  3. The program prints Rome.

readonly protects the property itself, but not the object it holds. To lock the city too, mark it as well: address: { readonly city: string }.

Common mistake

readonly was put before a variable so that it could not change:

TypeScript
readonly let total = 5; // ✗
console.log(total);
TEXT
error TS1024: 'readonly' modifier can only appear on a property declaration or index signature.

The compiler answers that readonly goes only before a property in a type declaration. For a variable that must not be reassigned, JavaScript already has const: const total = 5;.

Key points

  • readonly before a property lets it be set when the object is created and forbids writes later: error TS2540.
  • Only tsc checks readonly: JavaScript has no such thing, and the write works when the program runs.
  • readonly protects one level: the properties of a nested object stay changeable.
  • const protects a variable, and readonly protects a property.