How to Use readonly in TypeScript

What you’ll build or solve

You’ll declare properties that cannot be changed after they are set.

When this approach works best

Using readonly works best when you:

  • Define identifiers such as IDs that must never change
  • Share objects across modules and want to prevent mutation
  • Protect arrays passed into functions from being modified

It also helps when modeling stable configuration or state.

This is a bad idea if the property is expected to change. Marking mutable data as readonly creates friction and unnecessary refactoring.

Prerequisites

  • TypeScript installed
  • A .ts file
  • Basic knowledge of interfaces and classes

Step-by-step instructions

Step 1: Add the readonly keyword to a property

Place readonly before a property name to prevent reassignment.

In an interface

interfaceUser {
readonly id:number;
  name:string;
}

In a type alias

typeProduct= {
readonly sku:string;
  price:number;
};

In a class

classOrder {
readonly orderId:number;

  constructor(orderId:number) {
this.orderId=orderId;
  }
}

You can assign a readonly property inside a constructor, but not after the object is created.

With arrays

letnumbers:readonlynumber[]= [1,2,3];

numbers[0]=10;// Error
numbers.push(4);// Error

The readonly keyword works the same way in all these contexts. It blocks reassignment at compile time.

What to look for

  • readonly prevents reassignment, not reading
  • In classes, assignment is allowed in the constructor only
  • readonly Type[] blocks array mutations like push
  • readonly does not freeze the object at runtime
  • For transforming an entire type to read-only, use the Readonly<T> utility type from the utility types guide
  • For runtime immutability, use Object.freeze()

Examples you can copy

Example 1: Stable identifier

interfaceAccount {
readonly id:number;
  email:string;
}

constaccount:Account= {
  id:101,
  email:"alex@example.com"
};

account.id=202;// Error

The id remains fixed.

Example 2: Protected array parameter

functionprintScores(scores:readonlynumber[]) {
returnscores.join(", ");
}

The function cannot modify the incoming array.

Example 3: Class with immutable property

classUserProfile {
readonly userId:string;
  displayName:string;

  constructor(userId:string,displayName:string) {
this.userId=userId;
this.displayName=displayName;
  }
}

userId stays constant after construction.

Common mistakes and how to fix them

Mistake 1: Expecting runtime immutability

You might assume:

constuser:User= { id:1, name:"Sam" };

Why it breaks: readonly is enforced by TypeScript at compile time. JavaScript can still mutate objects at runtime.

Correct approach:

constfrozenUser=Object.freeze({ id:1, name:"Sam" });

Use Object.freeze() if you need runtime protection.

Mistake 2: Making the property read-only but not the array contents

You might write:

interfaceCart {
readonly items:string[];
}

Why it breaks: The items reference cannot change, but the array itself can still be modified.

Correct approach:

interfaceCart {
readonly items:readonlystring[];
}

This prevents both reassignment and array mutation.

Troubleshooting

  • If you see “Cannot assign to property because it is a read-only property,” remove readonly if mutation is required.
  • If array elements still change, confirm you used readonly Type[].
  • If behavior differs at runtime, remember that readonly does not freeze objects.
  • If you need full object immutability, combine compile-time checks with Object.freeze().

Quick recap

  • Add readonly before a property to block reassignment
  • It works in interfaces, type aliases, classes, and arrays
  • Assignment is allowed in class constructors
  • readonly is compile-time only
  • Use Readonly<T> for full-type transformation
  • Use Object.freeze() for runtime immutability