How to fix readonly property cannot be assigned to in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 11, 2025· 111 views

This TypeScript error occurs when you attempt to modify a property that has been marked as readonly. The readonly modifier prevents reassignment after initialization, enforcing immutability at the type level. Fixes include removing the readonly modifier if appropriate, creating new objects instead of mutating existing ones, or using utility types to work with mutable versions.

Quick fix

Fixes TS2540

// Interface with readonly properties
interface User {
  readonly id: number;      // This property is readonly
  readonly name: string;    // This property is readonly
  email: string;           // This property is mutable
}

const user: User = { id: 1, name: "John", email: "john@example.com" };
user.id = 2; // Error: readonly property cannot be assigned to
  1. Identify which property is readonly
  2. Remove readonly modifier if mutation is intentional
  3. Create new objects instead of mutating (immutable pattern)
  4. Use Mutable utility type for temporary mutations
typescript — error
// Interface with readonly properties
interface User {
readonly id: number; // This property is readonly
readonly name: string; // This property is readonly
email: string; // This property is mutable
}
const user: User = { id: 1, name: "John", email: "john@example.com" };
user.id = 2; // Error: readonly property cannot be assigned to
readonly property cannot be assigned to

What this error means

In TypeScript, the `readonly` keyword is a type modifier that marks properties as immutable after their initial assignment. This is a compile-time safety feature that helps prevent accidental mutations and enforces immutability patterns. When you declare a property as `readonly`, TypeScript enforces these rules: 1. **Initialization**: The property must be assigned a value at declaration or in the constructor (for classes) 2. **No reassignment**: Once initialized, the property cannot be assigned a new value 3. **Type-level enforcement**: This is purely a TypeScript compile-time check; JavaScript runtime allows the assignment The error "readonly property cannot be assigned to" (TS2540) occurs when you try to modify a readonly property after its initial assignment. This helps catch bugs where you might unintentionally change values that should remain constant throughout your program's execution.

Typical symptoms

  • TypeScript compiler shows 'readonly property cannot be assigned to' error
  • You're trying to modify an object property that was declared as readonly
  • Error appears when assigning to properties outside of their initialization context
  • IDE shows red squiggly line under the assignment operation
  • The error specifically mentions a property name that cannot be reassigned
  • Code compiles to JavaScript without issues but fails TypeScript type checking
  • Error occurs in class methods when trying to modify readonly instance properties

Common causes

  1. Property is explicitly marked with the readonly modifier in interface/type definitions
  2. Property comes from a type that uses the Readonly<T> utility type
  3. Class property is declared as readonly and you're trying to modify it outside the constructor
  4. You're attempting to mutate a readonly array or tuple
  5. Object literal was created with 'as const' assertion, making all properties readonly
  6. Inherited readonly properties from parent classes are being modified in child classes
  7. Using third-party library types that have readonly properties
  8. Array methods like push/pop are called on readonly arrays

How to fix "readonly property cannot be assigned to"

1Identify which property is readonly

First, locate the property causing the error. Look for the readonly keyword in type definitions:

typescript
// Interface with readonly properties
interface User {
  readonly id: number;      // This property is readonly
  readonly name: string;    // This property is readonly
  email: string;           // This property is mutable
}

const user: User = { id: 1, name: "John", email: "john@example.com" };
user.id = 2; // Error: readonly property cannot be assigned to

In classes:

typescript
class Config {
  readonly apiUrl: string;  // Readonly property
  timeout: number;          // Mutable property

  constructor(url: string) {
    this.apiUrl = url; // OK: assignment in constructor
    this.timeout = 3000;
  }
}

const config = new Config("https://api.example.com");
config.apiUrl = "https://new.com"; // Error: readonly property cannot be assigned to

Check the error message to identify the exact property name.

2Remove readonly modifier if mutation is intentional

If you genuinely need to mutate the property and immutability isn't required, remove the readonly keyword:

typescript
// Before: readonly prevents mutation
interface User {
  readonly id: number;
  readonly name: string;
}

// After: properties are mutable
interface User {
  id: number;      // Now writable
  name: string;    // Now writable
}

const user: User = { id: 1, name: "John" };
user.id = 2;       // Now allowed
user.name = "Jane"; // Now allowed

Considerations:
- Only remove readonly if the property truly needs to change
- Preserve readonly for properties that represent immutable identifiers (like IDs, keys, constants)
- Document why the property needs to be mutable

3Create new objects instead of mutating (immutable pattern)

Instead of modifying readonly properties, create new objects with updated values using spread syntax:

typescript
interface User {
  readonly id: number;
  readonly name: string;
  readonly email: string;
}

const user: User = { id: 1, name: "John", email: "john@example.com" };

// Don't mutate - create a new object
const updatedUser: User = {
  ...user,
  email: "john.new@example.com", // New value for email
};

// Original user remains unchanged
console.log(user.email); // "john@example.com"
console.log(updatedUser.email); // "john.new@example.com"

For nested objects:

typescript
interface Settings {
  readonly theme: string;
  readonly preferences: {
    readonly notifications: boolean;
    readonly language: string;
  };
}

const settings: Settings = {
  theme: "dark",
  preferences: {
    notifications: true,
    language: "en"
  }
};

// Create new object with updated nested property
const newSettings: Settings = {
  ...settings,
  preferences: {
    ...settings.preferences,
    language: "fr" // Update nested property
  }
};

This approach follows functional programming principles and avoids side effects.

4Use Mutable utility type for temporary mutations

Create a utility type that strips readonly modifiers when you need temporary mutability:

typescript
// Utility type to make all properties mutable
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

interface User {
  readonly id: number;
  readonly name: string;
}

// Create a mutable version for local manipulation
const user: Mutable<User> = { id: 1, name: "John" };
user.id = 2;         // Allowed with Mutable type
user.name = "Jane";  // Allowed with Mutable type

// Convert back to readonly when done
const readonlyUser: User = user;

For partial mutability:

typescript
// Make specific properties mutable
type MakeMutable<T, K extends keyof T> = Omit<T, K> & {
  [P in K]: T[P];
};

interface User {
  readonly id: number;
  readonly name: string;
  readonly email: string;
}

// Only make email mutable
type UserWithMutableEmail = MakeMutable<User, 'email'>;

const user: UserWithMutableEmail = { id: 1, name: "John", email: "john@example.com" };
user.email = "new@example.com"; // Allowed
// user.id = 2; // Still error: readonly

This approach maintains type safety while allowing controlled mutations.

5Handle readonly arrays and tuples

For readonly arrays, use non-mutating methods or create new arrays:

typescript
const readonlyArray: readonly string[] = ["a", "b", "c"];

// Error: push modifies the array
// readonlyArray.push("d");

// Solution 1: Create new array
const newArray = [...readonlyArray, "d"];

// Solution 2: Use non-mutating methods
const filtered = readonlyArray.filter(item => item !== "b");

// Solution 3: Convert to mutable temporarily
const mutableArray: string[] = [...readonlyArray];
mutableArray.push("d");

For readonly tuples:

typescript
const readonlyTuple: readonly [number, string] = [1, "a"];

// Error: cannot modify tuple elements
// readonlyTuple[0] = 2;

// Create new tuple instead
const newTuple: readonly [number, string] = [2, readonlyTuple[1]];

Remember: readonly string[] is different from ReadonlyArray<string> but both enforce immutability.

6Use type assertions as last resort (with caution)

If you must bypass TypeScript's readonly check, use type assertions sparingly:

typescript
interface User {
  readonly id: number;
  readonly name: string;
}

const user: User = { id: 1, name: "John" };

// Type assertion to bypass readonly
(user as { id: number; name: string }).id = 2;

// More direct (but less safe)
(user as any).id = 3;

// Safer: assert to a mutable version of the same type
type MutableUser = { id: number; name: string };
(user as MutableUser).name = "Jane";

Warnings:
- Type assertions bypass TypeScript's safety checks
- They don't change runtime behavior - JavaScript will still allow the mutation
- Use only when absolutely necessary and document why
- Consider if your design actually needs mutable properties instead

Better alternatives:
1. Refactor to use mutable types from the start
2. Use the Mutable utility type pattern
3. Create new objects instead of mutating

Advanced notes

### Readonly at Compile Time vs Runtime

The readonly modifier is purely a TypeScript compile-time construct. JavaScript has no equivalent concept:

typescript
// TypeScript compile-time error
interface Point {
  readonly x: number;
  readonly y: number;
}

const point: Point = { x: 1, y: 2 };
point.x = 3; // TS2540: readonly property cannot be assigned to
javascript
// Compiled JavaScript (runs without error)
const point = { x: 1, y: 2 };
point.x = 3; // Actually works!

This means:
- Readonly provides development-time safety but not runtime protection
- For true runtime immutability, use Object.freeze()
- Type assertions can bypass readonly checks, so use them cautiously

### Readonly with 'as const' Assertions

The as const assertion makes all properties deeply readonly:

typescript
const config = {
  api: {
    url: "https://api.example.com",
    timeout: 5000
  },
  features: ["auth", "upload"]
} as const;

// All properties become readonly
// config.api.url = "new-url"; // Error
// config.features.push("new"); // Error

### Readonly in Class Constructors

In classes, readonly properties can only be assigned in the constructor:

typescript
class Database {
  readonly connectionString: string;
  readonly maxConnections: number;

  constructor(conn: string, max: number = 10) {
    // Only place where readonly properties can be assigned
    this.connectionString = conn;
    this.maxConnections = max;
  }

  updateConnection(newConn: string) {
    // this.connectionString = newConn; // Error!
    // Instead, return a new instance
    return new Database(newConn, this.maxConnections);
  }
}

### Built-in Readonly Utilities

TypeScript provides several built-in utilities:

typescript
// Make all properties readonly
type ReadonlyUser = Readonly<{
  id: number;
  name: string;
}>;

// Readonly arrays (two equivalent syntaxes)
type ReadonlyStrings = readonly string[];
type AlsoReadonlyStrings = ReadonlyArray<string>;

// Readonly maps and sets
type ReadonlyMapType = ReadonlyMap<string, number>;
type ReadonlySetType = ReadonlySet<string>;

### Performance Considerations

- Readonly has zero runtime cost - it's erased during compilation
- Creating new objects instead of mutating has memory overhead
- For performance-critical code, consider mutable designs with careful documentation
- Use readonly for public APIs to enforce contracts
- Use mutable types internally where performance matters

### Testing Readonly Code

When testing code with readonly properties:
1. Test that readonly properties cannot be reassigned (compile-time)
2. Test immutable operations create correct new objects
3. Use type assertions in tests to verify runtime behavior
4. Consider using Object.freeze() in tests to catch runtime mutations

typescript
// Test that demonstrates readonly behavior
interface TestData {
  readonly value: number;
}

function processData(data: TestData): TestData {
  // Should return new object, not mutate
  return { value: data.value + 1 };
}

// Runtime check (optional)
const frozen = Object.freeze({ value: 1 });
const result = processData(frozen); // Should not throw

Frequently asked questions

What does "TS2540" mean?
In TypeScript, the readonly keyword is a type modifier that marks properties as immutable after their initial assignment. This is a compile-time safety feature that helps prevent accidental mutations and enforces immutability patterns. When you declare a property as readonly, TypeScript enforces these rules: 1.
How do I fix "TS2540"?
This TypeScript error occurs when you attempt to modify a property that has been marked as readonly. The readonly modifier prevents reassignment after initialization, enforcing immutability at the type level. Fixes include removing the readonly modifier if appropriate, creating new objects instead of mutating existing ones, or using utility types to work with mutable versions.
What causes "TS2540"?
Property is explicitly marked with the readonly modifier in interface/type definitions Property comes from a type that uses the Readonly<T> utility type Class property is declared as readonly and you're trying to modify it outside the constructor
Is the "TS2540" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading