TypeScript’s readonly
modifier is a powerful feature that helps prevent accidental modifications to properties and arrays. If you’re looking to write more predictable and safer TypeScript code, understanding readonly
is essential.
Typescript’s type system becomes even more robust when you can guarantee that certain values won’t change after their initial assignment. This is where the readonly
modifier comes into play, providing immutability at the type level.
Table of Contents
- What is the readonly Modifier?
- Using readonly with Object Properties
- Readonly Arrays
- The Readonly Utility Type
- Benefits of Using readonly
- Best Practices
- Common Gotchas and Solutions
- Conclusion
What is the readonly Modifier?
The readonly
modifier in TypeScript prevents properties from being changed after their initial assignment. It’s a way to make properties immutable at compile time, helping catch potential bugs before they happen.
Using readonly with Object Properties
Here’s how to use readonly
with object properties:
interface User {
readonly id: number;
name: string;
email: string;
}
let user: User = {
id: 1,
name: 'John',
email: '[email protected]'
}
// This will cause a compile error
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
Code language: PHP (php)
Readonly Arrays
Typescript also provides a ReadonlyArray<T>
type that makes an entire array immutable:
let numbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];
// These will cause compile errors
// numbers.push(6); // Error
// numbers[0] = 10; // Error
// numbers.length = 3; // Error
Code language: JavaScript (javascript)
The Readonly Utility Type
Typescript includes a utility type called Readonly<T>
that makes all properties of an object readonly:
interface Config {
host: string;
port: number;
timeout: number;
}
const config: Readonly<Config> = {
host: 'localhost',
port: 3000,
timeout: 5000
};
// This will cause a compile error
// config.port = 8080; // Error: Cannot assign to 'port' because it is a read-only property
Code language: PHP (php)
Benefits of Using readonly
1. Prevents Accidental Modifications
When you mark properties as readonly
, TypeScript will catch any attempts to modify them after initialization:
interface Point {
readonly x: number;
readonly y: number;
}
const point: Point = { x: 0, y: 0 };
// point.x = 1; // Error: Cannot assign to 'x' because it is a read-only property
Code language: PHP (php)
2. Better Code Documentation
Using readonly
serves as self-documenting code, making it clear which values should not be modified:
interface ApiConfig {
readonly apiKey: string; // Clearly shows this shouldn't change
readonly baseUrl: string; // Same here
timeout?: number; // This can be modified
}
Code language: PHP (php)
3. Improved Type Safety
The readonly
modifier helps catch potential bugs at compile time rather than runtime:
function processUser(user: Readonly<User>) {
// TypeScript will prevent any modifications to user properties
// This helps catch bugs early in development
}
Code language: JavaScript (javascript)
Best Practices
When to Use readonly
- Configuration objects that shouldn’t change after initialization:
interface DatabaseConfig {
readonly host: string;
readonly port: number;
readonly username: string;
readonly password: string;
}
Code language: PHP (php)
- Entity IDs and other permanent identifiers:
interface Employee {
readonly id: string;
readonly ssn: string;
name: string; // This can change
position: string; // This can change
}
Code language: PHP (php)
- Immutable data structures:
type Vector = Readonly<{
x: number;
y: number;
z: number;
}>
Code language: HTML, XML (xml)
Common Gotchas and Solutions
Nested Objects
Remember that readonly
is shallow by default. Nested objects can still be modified:
interface NestedConfig {
readonly api: {
url: string;
key: string;
}
}
const config: NestedConfig = {
api: {
url: 'http://api.example.com',
key: 'secret'
}
};
// This is still allowed!
config.api.url = 'new-url'; // No error
Code language: PHP (php)
To make nested objects completely immutable, use Readonly
recursively or consider using a deep freeze utility:
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
}
const config: DeepReadonly<NestedConfig> = {
api: {
url: 'http://api.example.com',
key: 'secret'
}
};
// Now this will cause an error
// config.api.url = 'new-url'; // Error: Cannot assign to 'url' because it is a read-only property
Code language: JavaScript (javascript)
Conclusion
The readonly
modifier is a powerful feature in TypeScript that helps create more predictable and maintainable code by preventing accidental modifications to properties and arrays. By understanding when and how to use readonly
, you can write safer TypeScript code with fewer runtime bugs.
Whether you’re working with configuration objects, entity IDs, or immutable data structures, readonly
provides the type-level immutability guarantees that help catch potential issues during development rather than in production.
As you continue your TypeScript journey, consider exploring more advanced type system features like Type Assertions or Type Guards to further enhance your code’s type safety.