Skip to main content

Immutable fields

1. The readonly keyword

To make a property immutable, you write the readonly keyword before its name in an interface or a type.


In an interface

javascript
interface User { readonly id: number; name: string; } const user: User = { id: 1, name: "Tim" }; user.name = "Max"; // can be changed user.id = 2; // Error: read-only property

readonly guarantees that id cannot be reassigned after initialization. Attempting to change the value causes a compilation error.


In a type (type)

javascript
type Product = { readonly sku: string; title: string; }; const item: Product = { sku: "A-123", title: "Linen shirt" }; item.title = "Cotton shirt"; // allowed item.sku = "B-456"; // Error

Works exactly the same way as in interfaces.


2. Immutability applies only at the top level

TypeScript makes readonly apply only to the property itself, but does not protect nested objects:

javascript
interface Profile { readonly user: { name: string; }; } const p: Profile = { user: { name: "Tim" } }; p.user = { name: "Max" }; // Error (cannot reassign user) p.user.name = "Max"; // allowed (nested fields can be changed)

In other words, "deep" immutability is not created automatically.


3. How to make all fields immutable

TypeScript provides a built-in utility type Readonly<T>, which turns every property of type T into readonly.

javascript
interface User { id: number; name: string; } type ImmutableUser = Readonly<User>; const user: ImmutableUser = { id: 1, name: "Tim" }; user.id = 2; // Error user.name = ""; // Error

Readonly<T> automatically adds readonly before every field of the interface.


4. How to make "deep" immutability (nested fields too)

Out of the box, TypeScript only gives you shallow immutability. If you need to prevent changes to nested objects, you use recursive utilities, for example DeepReadonly.

Example implementation:

javascript
type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]>; };

Example usage:

javascript
interface User { id: number; profile: { name: string; address: { city: string; }; }; } type FrozenUser = DeepReadonly<User>; const u: FrozenUser = { id: 1, profile: { name: "Tim", address: { city: "LA" } }, }; u.id = 2; // Error u.profile.name = "Max"; // Error u.profile.address.city = "NY"; // Error

Now the object and all its nested fields are truly immutable.


5. readonly also works with arrays

You can make an array read-only using readonly or ReadonlyArray<T>:

javascript
const nums: readonly number[] = [1, 2, 3]; nums.push(4); // Error const names: ReadonlyArray<string> = ["Tim", "Max"]; names[0] = "Anton"; // Error

Such an array cannot be modified (elements cannot be added, removed, or changed), but it can be read.


6. Summary table

What you needHow to write itExample
One read-only propertyreadonly id: numberinterface User { readonly id: number }
All properties readonlyReadonly<Type>type Immutable = Readonly<User>
Deep immutabilityDeepReadonly<T>recursive type
Read-only arrayreadonly T[] or ReadonlyArray<T>readonly number[]

Summary

In TypeScript, immutable properties are defined through readonly.

In an interface:

javascript
interface User { readonly id: number }

In a type:

javascript
type User = { readonly id: number }

For all fields:

javascript
type Frozen = Readonly<User>

This helps write reliable, predictable, and safe code, protecting data from accidental changes.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.