readonly in TypeScript
What is TypeScript readonly
The readonly modifier in TypeScript is used to mark properties as immutable after their initial assignment. This immutability applies to properties within classes, interfaces, and types. By using readonly, developers can prevent accidental modifications to these properties, leading to more maintainable and error-resistant code.
TypeScript readonly in Classes
In classes, the readonly modifier ensures that a property can be assigned a value only once, either during its declaration or within the constructor. Attempting to modify a readonly property outside these contexts results in a compile-time error.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13class Employee { readonly empCode: number; empName: string; constructor(code: number, name: string) { this.empCode = code; this.empName = name; } } let emp = new Employee(10, "John"); emp.empCode = 20; // Compiler Error: Cannot assign to 'empCode' because it is a read-only property. emp.empName = 'Bill'; // This is allowed since empName is not readonly.
In this example, empCode is a readonly property. It can be initialized either at its declaration or within the constructor. Any attempt to modify empCode after the object has been instantiated will result in a compile-time error.
Implement readonly Interfaces
Interfaces can also define readonly properties, ensuring that implementing objects cannot modify these properties once they are set.
Example:
1 2 3 4 5 6 7 8 9 10 11interface IEmployee { readonly empCode: number; empName: string; } let empObj: IEmployee = { empCode: 1, empName: "Steve" }; empObj.empCode = 100; // Compiler Error: Cannot assign to 'empCode' because it is a read-only property.
Here, empCode is marked as readonly within the IEmployee interface. While it can be assigned a value when creating the object empObj, any subsequent attempts to modify empCode will trigger a compile-time error.
The Readonly<T> Utility Type
TypeScript provides a utility type Readonly<T> that transforms all properties of a type T into readonly properties. This is particularly useful when you want to enforce immutability across an entire object type without individually marking each property as readonly.
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20interface IEmployee { empCode: number; empName: string; } let emp1: Readonly<IEmployee> = { empCode: 1, empName: "Steve" }; emp1.empCode = 100; // Compiler Error: Cannot assign to 'empCode' because it is a read-only property. emp1.empName = 'Bill'; // Compiler Error: Cannot assign to 'empName' because it is a read-only property. let emp2: IEmployee = { empCode: 1, empName: "Steve" }; emp2.empCode = 100; // This is allowed since emp2 is not Readonly<IEmployee>. emp2.empName = 'Bill'; // This is allowed since emp2 is not Readonly<IEmployee>.
In this example, emp1 is of type Readonly<IEmployee>, making all its properties immutable. Any attempt to modify empCode or empName results in a compile-time error. Conversely, emp2 is a regular IEmployee object, allowing property modifications.
Readonly Arrays and Tuples
TypeScript extends the concept of immutability to arrays and tuples using the readonly modifier. A ReadonlyArray<T> is an array that cannot be modified after creation, preventing operations like push, pop, or direct element assignment.
Example:
1 2 3let arr: ReadonlyArray<number> = [1, 2, 3]; arr.push(4); // Compiler Error: Property 'push' does not exist on type 'readonly number[]'. arr[0] = 10; // Compiler Error: Index signature in type 'readonly number[]' only permits reading.
Similarly, TypeScript 3.4 introduced the ability to declare readonly tuples, ensuring that the contents of the tuple remain unchanged.
Example:
1 2let point: readonly [number, number] = [0, 0]; point[0] = 1; // Compiler Error: Index signature in type 'readonly [number, number]' only permits reading.
By using ReadonlyArray and readonly tuples, developers can enforce immutability on array and tuple data structures, preventing unintended modifications.
Limitations and Considerations
While the readonly modifier prevents reassignment of properties, it does not make the value itself immutable, especially if the value is an object. This means that while you cannot reassign the property to a new object, you can still modify the internal state of the object.
Example:
1 2 3 4 5 6 7 8 9 10interface Home { readonly resident: { name: string; age: number }; } const home: Home = { resident: { name: "John", age: 30 } }; home.resident.age = 31; // This is allowed. home.resident = { name: "Doe", age: 40 }; // Compiler Error: Cannot assign to 'resident' because it is a read-only property.
In this case, while the resident property is readonly and cannot be reassigned, the properties of the resident object itself can still be modified. To achieve true immutability, you would need to use additional tools or techniques such as freezing the object using Object.freeze().
Example with Object.freeze():
1 2const frozenResident = Object.freeze({ name: "John", age: 30 }); frozenResident.age = 31; // This results in a runtime error in strict mode.
This ensures that both the object reference and its internal state cannot be modified.
Practical Examples
Example 1: Combine readonly with Constructor Parameters
Using readonly directly on constructor parameters reduces boilerplate code by automatically creating and initializing readonly properties.
Code Example:
1 2 3 4 5 6 7 8class Student { constructor(public readonly id: number, public name: string) {} } const student = new Student(1, "Alice"); console.log(student.id); // Output: 1 student.name = "Bob"; // Allowed student.id = 2; // Compiler Error: Cannot assign to 'id' because it is a read-only property.
Here, id is automatically a readonly property without explicitly declaring it.
Example 2: Readonly with Nested Objects
The Readonly<T> utility type applies only to the top level of the object. If you need deep immutability, you would need a recursive solution or libraries like Immutable.js.
Code Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15interface Person { readonly name: string; readonly address: { street: string; city: string; }; } const person: Readonly<Person> = { name: "John", address: { street: "123 Elm St", city: "Springfield" }, }; person.address.street = "456 Oak St"; // This is allowed because `Readonly` is shallow. person.address = { street: "789 Pine St", city: "Rivertown" }; // Compiler Error
Example 3: ReadonlyArray Usage in Functions
Functions that should not modify an array passed to them can use ReadonlyArray to enforce immutability.
Code Example:
1 2 3 4 5 6 7function displayNames(names: ReadonlyArray<string>): void { names.push("New Name"); // Compiler Error console.log(names.join(", ")); } const names: ReadonlyArray<string> = ["Alice", "Bob", "Charlie"]; displayNames(names);
This approach ensures that the original array remains unchanged.
Conclusion
The readonly modifier in TypeScript is a versatile and essential tool for enforcing immutability in your code. By marking properties, arrays, and types as readonly, you can prevent accidental modifications, enhance code safety, and create more predictable software.
Key Takeaways
- Use
readonlyin classes, interfaces, and arrays to enforce immutability. - Leverage the
Readonly<T>utility type for transforming all properties of an object type intoreadonly. - Remember that
readonlyis shallow; additional measures are required for deep immutability. - Use
ReadonlyArrayandreadonlytuples to handle immutable lists and fixed-size collections.
Frequently Asked Questions
The readonly modifier ensures that a property cannot be reassigned after its initial assignment, making it immutable from the outside.
readonly prevents reassignment of properties but doesn’t make the object’s internal state immutable. True immutability requires techniques like Object.freeze() or specialized libraries.
TypeScript doesn’t have a readonly function concept, but readonly can be used to ensure methods in a class or object properties holding functions cannot be reassigned.
const prevents reassignment of variables, as const makes values deeply readonly as literal types, and readonly ensures class or object properties cannot be reassigned after initialization.
You can convert a type to readonly in TypeScript using the Readonly<T> utility type, which makes all properties of the given type T immutable.
Still have questions?Contact our support team