How To Use Readonly in TypeScript
Exploring the power of built-in utility types for safer code

TypeScript has introduced a variety of utility types, designed to help transform and manipulate types in more advanced ways, allowing developers to write safer and more robust code. One of these powerful utility types is Readonly<Type>. In this article, we’ll take a deep dive into Readonly and see how it can supercharge your TypeScript experience.
What Is Readonly?
The Readonly utility type, released with TypeScript 2.1, constructs a type with all properties of Type set to readonly. This means the properties of the constructed type cannot be reassigned.
Consider the following code:
type Todo = {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
}
todo.title = "Hello"
// Error: Cannot assign to 'title' because it is a read-only property.In the example above, we have a Todo type with a single property title. We then create an object todo, of type Readonly<Todo>. As a result, TypeScript ensures that the properties of todo cannot be reassigned after initialization, enforcing immutability at the type level.
This utility is particularly useful for representing assignment expressions that would fail at runtime, such as when attempting to reassign properties of a frozen object.
Practical Use Case: The Power of Immutability
Let’s say you’re building a function that accepts a configuration object. This function is used across your codebase, and to prevent bugs, you want to ensure that this configuration object is not accidentally mutated inside the function.
Consider the following example:
interface Config {
apiUrl: string;
timeout: number;
}
function performRequest(config: Config) {
// ... perform some request
}
const config: Config = {
apiUrl: "https://api.example.com",
timeout: 5000
}
performRequest(config)In this code, there’s nothing stopping performRequest from modifying the config object. To ensure config remains unchanged, we can use the Readonly utility type:
function performRequest(config: Readonly<Config>) {
// ... perform some request
// config.apiUrl = "https://api.other.com"; // Error: Cannot assign to 'apiUrl' because it is a read-only property.
}Now, TypeScript will throw an error if we try to modify the config object inside performRequest, preventing possible bugs and making our code safer.
Comparing Readonly and “as const”
TypeScript offers developers a variety of tools to help ensure data immutability. Two such tools are Readonly and as const. Although they can be used interchangeably in some scenarios, they are fundamentally different and each has its own unique use cases.
Understanding Readonly
Readonly is a utility type provided by TypeScript. It creates a type that has all the same properties as the input type, but each property is marked as readonly. This effectively means that you cannot reassign the properties of an object of this type once it's been created.
Here’s a simple example of how it works:
type Person = {
name: string;
age: number;
}
const john: Readonly<Person> = {
name: "John",
age: 30,
}
john.age = 31
// Error: Cannot assign to 'age' because it is a read-only property.In the example above, john is of type Readonly<Person>, which means all of its properties are readonly. Attempting to reassign any of these properties results in a TypeScript error.
The Power of “as const”
as const is a little more complex. It is not a type, but a type assertion. When you use as const, you're telling TypeScript that you want the narrowest possible type to be inferred for your variable. This means:
- Variables are inferred to be of a literal type, not a broader type.
- Object properties are inferred as readonly.
- Arrays are inferred to be readonly tuples.
Here’s an example to illustrate:
const john = {
name: "John",
age: 30,
} as const
john.age = 31
// Error: Cannot assign to 'age' because it is a read-only property.In this example, john is inferred to be of a type that has readonly properties name and age, with specific literal values "John" and >30. This means you cannot change the properties once they're assigned, similar to Readonly.
For this reason, I tend to get these concepts mixed up in my head.
However, the difference is that as const also affects variables and arrays, not just the properties of objects:
const age = 30 as const;
age = 31
// Error: Cannot assign to 'age' because it is a read-only property.In this example, age is inferred to be of type 30, not number. Attempting to change its value results in a TypeScript error.
const numbers = [1, 2, 3] as const
numbers.push(4)
// Error: Property 'push' does not exist on type 'readonly [1, 2, 3]'.Here, numbers is inferred to be a readonly tuple of [1, 2, 3], not an array of number[]. This means you can't modify the array using methods like push, or you’ll get an error that the array method only doesn’t exist.
When to Use Readonly vs “as const”
Use Readonly when you want to create a type where all properties of an object are readonly. It’s particularly useful when creating interfaces or types that will be used across your codebase to enforce immutability.
Use as const when you want the narrowest type to be inferred for a variable, whether it's a single variable, an array, or an object. It's most useful when you're declaring constants or configuration objects where the values and structure won't change.
Remember, as const is a type assertion, so it won't create a new type that you can use elsewhere in your code. If you need to reuse a readonly type in multiple places, it’s better to create a Readonly type or interface.
To summarize, Readonly and as const are powerful tools in TypeScript for enforcing immutability. While Readonly is a utility type that makes all properties of an object readonly, as const is a type assertion that infers the narrowest possible type for a variable, making variables, object properties, and arrays readonly. Therefore, the choice between the two will largely depend on your specific use case.
Consider the following scenarios:
- If you are working with an object and want to prevent its properties from being modified, both
Readonlyandas constcan be used. But if the object type is to be reused in multiple places,Readonlyis a better choice. - For individual variables, especially when dealing with constant values,
as constis the way to go as it infers a literal type, preventing any changes to the variable. - If you are dealing with an array and want to prevent any modifications to it,
as constshould be used because it makes the array readonly and also infers it as a tuple with fixed length and types.
Ultimately, understanding the differences between Readonly and as const and their appropriate use cases will allow you to write more secure, predictable, and error-resistant TypeScript code.
Wrapping Up: Readonly CONSTANTS in TypeScript
The Readonly utility type is a powerful tool in TypeScript's utility type arsenal, allowing us to enforce immutability in our types and prevent bugs due to unwanted mutation. This is just one of many utility types available in TypeScript. Explore the TypeScript Docs to learn about more of these powerful tools.
Remember, TypeScript is not just about providing static types for JavaScript, but also about making your code safer, more readable, and easier to refactor. So next time you’re about to write a TypeScript function, think about how utility types like Readonly can be used to make your code safer and more robust.

