- Abstract class
- Annotations
- Array
- Asserts
- Casting
- Class
- Conditional types
- Const
- Date object
- Decorators
- Default parameter
- Dictionary
- Enum
- Exclude type
- Extract type
- For loop
- forEach()
- Function
- Generics
- Index signature
- Infer
- Inheritance
- Interface
- 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:
const
prevents reassignment of a variablereadonly
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.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.