- 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 TypeScript readonly
utility type creates an immutable version of an object type, preventing properties from being reassigned after initialization. In readonly TypeScript
, immutability is a tool that improves type safety and code predictability, especially when working with configurations, constants, and function inputs where unintentional changes could lead to bugs.
This guide explores the purpose and usage of the TypeScript Readonly
type, shows examples with object types, arrays, and Record
types, and explains how it differs from the const
keyword. You’ll also learn how to remove readonly modifiers and work with advanced patterns like nested readonly structures.
What Is the TypeScript Readonly Type?
The Readonly
utility type makes all properties of an object type immutable. Once a value is assigned, it cannot be reassigned.
Here’s how it works internally:
ts
CopyEdit
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
This utility prepends the readonly
modifier to every property key in the object, creating a frozen shape.
Syntax of Readonly TypeScript
The syntax is:
Readonly<Type>
Example:
type User = {
id: number;
name: string;
};
type ReadonlyUser = Readonly<User>;
Now ReadonlyUser
is:
{
readonly id: number;
readonly name: string;
}
Attempting to reassign id
or name
will result in a compile-time error.
Why Use the TypeScript Readonly Type?
Using readonly TypeScript
protects objects from accidental mutation. This is especially valuable when:
- Passing data between components or functions
- Freezing configuration settings
- Sharing global constants
- Enforcing immutability in reducers or state stores
Immutability helps avoid unintended side effects and makes code easier to reason about, debug, and test.
Example: Enforcing Read-Only Props
type Settings = {
theme: string;
fontSize: number;
};
const defaultSettings: Readonly<Settings> = {
theme: "dark",
fontSize: 14
};
defaultSettings.fontSize = 16; // Error: Cannot assign to 'fontSize' because it is a read-only property.
This usage prevents accidental updates to defaultSettings
, ensuring consistent behavior throughout the app.
Readonly TypeScript with Arrays
You can create read-only arrays in TypeScript by using readonly
in two ways.
Option 1: Built-in syntax
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
This protects arrays from being mutated by preventing methods like push
, splice
, or direct assignment.
Readonly Record TypeScript Usage
You can combine 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: Cannot assign to 'user1'
The readonly record TypeScript
approach is useful for fixed dictionaries or enum-like maps that shouldn't change after being defined.
Const vs Readonly TypeScript
There’s often confusion between const
and readonly
.
const
applies to variables, preventing reassignment.readonly
applies to properties inside objects, preventing changes to those properties.
Example:
const person = {
name: "Alice"
};
person.name = "Bob"; // ✅ This is allowed because `name` is not readonly.
const person2: Readonly<{ name: string }> = {
name: "Charlie"
};
person2.name = "Dave"; // ❌ Error: Cannot assign to 'name'
So, const
freezes the reference. readonly
freezes the content.
Nested Readonly TypeScript
The built-in Readonly
only affects top-level properties. If your object contains nested structures, those inner properties will still be mutable.
Example:
type Profile = {
user: {
id: number;
name: string;
};
};
const profile: Readonly<Profile> = {
user: {
id: 1,
name: "Sam"
}
};
profile.user.name = "Alex"; // ✅ This is allowed
To deeply freeze an object, use a custom recursive 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: Cannot assign to 'name'
How to Remove Readonly in TypeScript
Sometimes you may need to reverse a readonly type and make all properties mutable again. Use this utility:
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
Example:
type ReadonlyUser = Readonly<{ id: number; name: string }>;
type EditableUser = Mutable<ReadonlyUser>;
Now EditableUser
allows updates to id
and name
.
Use Cases for Readonly TypeScript
API Response Models
If your app receives data from a server that shouldn't be modified directly, make it readonly to avoid accidental changes:
type ServerResponse = Readonly<{
id: string;
status: "ok" | "error";
}>;
Redux and State Management
Enforcing immutability in reducers:
type AppState = {
user: { name: string };
};
function reducer(state: Readonly<AppState>, action: any): AppState {
// state.user.name = "New" // ❌ Error
return state;
}
This ensures functional-style programming with predictable state changes.
Config and Environment Variables
Immutable configuration prevents accidental mutations:
const config: Readonly<{
baseUrl: string;
apiKey: string;
}> = {
baseUrl: "https://api.example.com",
apiKey: "123456"
};
Editor Support and Type Safety
The TypeScript readonly
modifier gives developers immediate feedback in their IDE when they try to mutate properties that shouldn’t change. Editors like VS Code:
- Highlight illegal mutations
- Provide autocomplete for readonly properties
- Improve code readability by signaling intent
Readonly code is often easier to debug, test, and maintain because data flow is more predictable.
Readonly Class Properties
In TypeScript classes, you can mark properties as readonly
too:
class User {
readonly id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
Once id
is set via the constructor, it cannot be changed later:
const user = new User(1, "Jane");
user.id = 2; // Error: Cannot assign to 'id'
This pattern is helpful for defining identity or configuration values that must remain unchanged.
Summary
The TypeScript readonly
utility type provides a clean way to enforce immutability in objects, arrays, records, and class properties. It helps avoid unintended mutations, leading to more predictable and bug-resistant code.
You can use readonly TypeScript
to freeze top-level properties or build recursive types like DeepReadonly
for nested structures. The combination of Readonly
, Record
, and utility types like Mutable
enables powerful type transformations.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.