TYPESCRIPT

TypeScript Readonly Type: Syntax, Use Cases, and Examples

The TypeScript readonly utility type creates an immutable version of an object type, preventing properties from being reassigned after initialization. In readonly TypeScript, immutability is a tool that improves type safety and code predictability, especially when working with configurations, constants, and function inputs where unintentional changes could lead to bugs.

This guide explores the purpose and usage of the TypeScript Readonly type, shows examples with object types, arrays, and Record types, and explains how it differs from the const keyword. You’ll also learn how to remove readonly modifiers and work with advanced patterns like nested readonly structures.


What Is the TypeScript Readonly Type?

The Readonly utility type makes all properties of an object type immutable. Once a value is assigned, it cannot be reassigned.

Here’s how it works internally:

ts
CopyEdit
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

This utility prepends the readonly modifier to every property key in the object, creating a frozen shape.


Syntax of Readonly TypeScript

The syntax is:

Readonly<Type>

Example:

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

type ReadonlyUser = Readonly<User>;

Now ReadonlyUser is:

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

Attempting to reassign id or name will result in a compile-time error.


Why Use the TypeScript Readonly Type?

Using readonly TypeScript protects objects from accidental mutation. This is especially valuable when:

  • Passing data between components or functions
  • Freezing configuration settings
  • Sharing global constants
  • Enforcing immutability in reducers or state stores

Immutability helps avoid unintended side effects and makes code easier to reason about, debug, and test.


Example: Enforcing Read-Only Props

type Settings = {
  theme: string;
  fontSize: number;
};

const defaultSettings: Readonly<Settings> = {
  theme: "dark",
  fontSize: 14
};

defaultSettings.fontSize = 16; // Error: Cannot assign to 'fontSize' because it is a read-only property.

This usage prevents accidental updates to defaultSettings, ensuring consistent behavior throughout the app.


Readonly TypeScript with Arrays

You can create read-only arrays in TypeScript by using readonly in two ways.

Option 1: Built-in syntax

const numbers: readonly number[] = [1, 2, 3];
numbers.push(4); // Error: Property 'push' does not exist on type 'readonly number[]'.

Option 2: ReadonlyArray generic

const strings: ReadonlyArray<string> = ["a", "b"];
strings[0] = "z"; // Error

This protects arrays from being mutated by preventing methods like push, splice, or direct assignment.


Readonly Record TypeScript Usage

You can combine Readonly with Record to build immutable key-value maps.

type StatusMap = Readonly<Record<string, "active" | "inactive">>;

const statuses: StatusMap = {
  user1: "active",
  user2: "inactive"
};

statuses.user1 = "inactive"; // Error: Cannot assign to 'user1'

The readonly record TypeScript approach is useful for fixed dictionaries or enum-like maps that shouldn't change after being defined.


Const vs Readonly TypeScript

There’s often confusion between const and readonly.

  • const applies to variables, preventing reassignment.
  • readonly applies to properties inside objects, preventing changes to those properties.

Example:

const person = {
  name: "Alice"
};

person.name = "Bob"; // ✅ This is allowed because `name` is not readonly.

const person2: Readonly<{ name: string }> = {
  name: "Charlie"
};

person2.name = "Dave"; // ❌ Error: Cannot assign to 'name'

So, const freezes the reference. readonly freezes the content.


Nested Readonly TypeScript

The built-in Readonly only affects top-level properties. If your object contains nested structures, those inner properties will still be mutable.

Example:

type Profile = {
  user: {
    id: number;
    name: string;
  };
};

const profile: Readonly<Profile> = {
  user: {
    id: 1,
    name: "Sam"
  }
};

profile.user.name = "Alex"; // ✅ This is allowed

To deeply freeze an object, use a custom recursive type:

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

Now:

const profile: DeepReadonly<Profile> = {
  user: {
    id: 1,
    name: "Sam"
  }
};

profile.user.name = "Alex"; // ❌ Error: Cannot assign to 'name'

How to Remove Readonly in TypeScript

Sometimes you may need to reverse a readonly type and make all properties mutable again. Use this utility:

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Example:

type ReadonlyUser = Readonly<{ id: number; name: string }>;
type EditableUser = Mutable<ReadonlyUser>;

Now EditableUser allows updates to id and name.


Use Cases for Readonly TypeScript

API Response Models

If your app receives data from a server that shouldn't be modified directly, make it readonly to avoid accidental changes:

type ServerResponse = Readonly<{
  id: string;
  status: "ok" | "error";
}>;

Redux and State Management

Enforcing immutability in reducers:

type AppState = {
  user: { name: string };
};

function reducer(state: Readonly<AppState>, action: any): AppState {
  // state.user.name = "New"  // ❌ Error
  return state;
}

This ensures functional-style programming with predictable state changes.

Config and Environment Variables

Immutable configuration prevents accidental mutations:

const config: Readonly<{
  baseUrl: string;
  apiKey: string;
}> = {
  baseUrl: "https://api.example.com",
  apiKey: "123456"
};

Editor Support and Type Safety

The TypeScript readonly modifier gives developers immediate feedback in their IDE when they try to mutate properties that shouldn’t change. Editors like VS Code:

  • Highlight illegal mutations
  • Provide autocomplete for readonly properties
  • Improve code readability by signaling intent

Readonly code is often easier to debug, test, and maintain because data flow is more predictable.


Readonly Class Properties

In TypeScript classes, you can mark properties as readonly too:

class User {
  readonly id: number;
  name: string;

  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }
}

Once id is set via the constructor, it cannot be changed later:

const user = new User(1, "Jane");
user.id = 2; // Error: Cannot assign to 'id'

This pattern is helpful for defining identity or configuration values that must remain unchanged.


Summary

The TypeScript readonly utility type provides a clean way to enforce immutability in objects, arrays, records, and class properties. It helps avoid unintended mutations, leading to more predictable and bug-resistant code.

You can use readonly TypeScript to freeze top-level properties or build recursive types like DeepReadonly for nested structures. The combination of Readonly, Record, and utility types like Mutable enables powerful type transformations.

Learn TypeScript for Free
Start learning now
button icon
To advance beyond this tutorial and learn TypeScript by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH

Reach your coding goals faster