- Abstract class
- Annotations
- Array
- Asserts
- Casting
- Class
- Comments
- Conditional types
- Const
- Data types
- Date object
- Decorators
- Default parameter
- Dictionary
- Enum
- Exclude type
- Extract type
- For loop
- forEach()
- Function
- Generic types
- Generics
- Index signature
- Infer
- Inheritance
- Interface
- Intersection types
- Keyof type operator
- Let
- Map type
- Mixin
- Module
- Namespace
- Never
- Object type
- Omit type
- Operator
- Optional parameter
- Partial type
- Pick type
- Promise
- Property
- Readonly type
- Record type
- Required type
- Satisfies operator
- Tuples
- Type alias
- Type assertion
- Type guard
- Type narrowing
- Typeof Type Operator
- Union
- Utility types
- Var
- Void
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:
constprevents reassignment of a variablereadonlyprevents 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.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.