TypeScript Readonly<T> Utility Type Explained

Learn how Readonly<T> makes all properties immutable at the type level. Includes examples for frozen objects, Redux state, and immutable data patterns.

Object Types

Detailed Explanation

Understanding Readonly

Readonly<T> constructs a type with all properties of T set to readonly. This means the properties of the resulting type cannot be reassigned after initialization.

Syntax

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

Basic Example

interface Point {
  x: number;
  y: number;
}

type ReadonlyPoint = Readonly<Point>;
// Equivalent to:
// {
//   readonly x: number;
//   readonly y: number;
// }

const p: ReadonlyPoint = { x: 10, y: 20 };
// p.x = 5; // Error: Cannot assign to 'x' because it is a read-only property

With Object.freeze()

Readonly<T> is the type-level equivalent of Object.freeze():

function freeze<T extends object>(obj: T): Readonly<T> {
  return Object.freeze(obj);
}

const config = freeze({ host: "localhost", port: 3000 });
// config.port = 8080; // Error!

Shallow vs Deep Readonly

Like Partial, Readonly is shallow. Nested objects are still mutable:

interface State {
  user: { name: string; age: number };
  items: string[];
}

type ReadonlyState = Readonly<State>;
const state: ReadonlyState = { user: { name: "A", age: 1 }, items: [] };
// state.user = { name: "B", age: 2 }; // Error
state.user.name = "B"; // OK! (nested mutation)

For deep immutability, you need a custom DeepReadonly type or use as const.

Use Case

Use Readonly<T> for Redux state types, frozen configuration objects, immutable data patterns, event payloads that should not be modified, and function parameters that should not be mutated.

Try It — TypeScript Utility Types

Open full tool