TYPESCRIPT

TypeScript Readonly Type: Syntax, Use Cases, and Examples

The readonly utility type in TypeScript creates an immutable version of an object type. It prevents properties from being reassigned after initialization, helping catch bugs early and making your code more predictable—especially in cases like configuration objects, constant mappings, and function parameters that shouldn’t be modified.


What Is the Readonly Type?

The Readonly<T> utility type transforms all properties of a given object type T into read-only properties. Once set, their values can’t be changed.

Internally, it looks like this:

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

This adds the readonly modifier to every property in the object, effectively freezing the structure at compile time.


Syntax

The syntax is simple:

Readonly<Type>

Example

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

type ReadonlyUser = Readonly<User>;

Now ReadonlyUser looks like this:

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

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


Why Use Readonly?

Using readonly in your type definitions prevents accidental mutations. This is especially useful when:

  • Passing data between components or functions
  • Freezing configuration settings
  • Working with constants or global objects
  • Enforcing immutability in state management or reducers

Immutability helps make your code more testable, understandable, and easier to maintain.


Example: Enforcing Immutable Settings

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

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

defaultSettings.fontSize = 16; // Error: Cannot assign to 'fontSize'

Using Readonly here guarantees that defaultSettings can't be accidentally changed elsewhere in your code.


Readonly with Arrays

Arrays can also be marked as read-only to prevent mutation.

Option 1: readonly keyword

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

Both methods block mutating methods like push, pop, or direct index assignments.


Combining Readonly with Record

You can use 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

This is helpful for static dictionaries that should stay constant across your app.


Readonly vs Const

const and readonly might seem similar, but they serve different purposes:

  • const prevents reassignment of a variable
  • readonly prevents reassignment of object properties

Example

const person = {
  name: "Alice"
};

person.name = "Bob"; // ✅ Allowed — 'person' is const, but 'name' is mutable

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

person2.name = "Dave"; // ❌ Error

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


Nested Readonly Structures

The standard Readonly only applies to the top level of an object. Nested objects remain mutable.

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

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

profile.user.name = "Alex"; // ✅ Allowed — 'user' is not deeply readonly

To make deeply nested structures immutable, you can define a recursive utility 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

Reverting a Readonly Type

To remove readonly from all properties in a type, you can define a Mutable helper:

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

Example:

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

EditableUser now allows id and name to be reassigned.


Practical Use Cases

API Responses

Use Readonly to prevent accidental mutation of data received from the server:

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

State Management

Enforce immutability in reducers:

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

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

Configuration Objects

Immutable config objects reduce the risk of runtime issues:

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

Readonly in Classes

You can use readonly in TypeScript classes to lock down properties after construction:

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

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

Once set, id can't be reassigned:

const user = new User(1, "Jane");
user.id = 2; // ❌ Error

This is useful for values that define identity or shouldn't change after instantiation.


Editor Support and Type Safety

Using readonly makes your code safer and easier to work with in modern editors:

  • You'll get warnings or red squiggles when trying to mutate readonly values
  • Autocomplete reflects the immutability of properties
  • Code is easier to reason about since fewer things can change unexpectedly

Readonly types also lead to better documentation and more reliable APIs.


Summary

The readonly utility type is one of the most practical tools in TypeScript for building safe and maintainable code. It locks down object properties, preventing unexpected changes and enabling predictable data flow.

You can apply it to objects, arrays, records, and even classes. For complex structures, use DeepReadonly. And if you ever need to reverse it, utility types like Mutable give you full control. By leveraging readonly types strategically, you'll write more stable and robust TypeScript code.

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