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.