Readonly
Look but don't touch โ immutability at the type level
Readonly<T> makes every property of a type
read-only โ you can READ values but you CANNOT modify them. It's
TypeScript's way of putting a HERITAGE LOCK on your data.
Analogy: The heritage seal on the Golconda Fort inscriptions. The
ASI (Archaeological Survey of India) puts a seal on ancient
inscriptions โ anyone can READ them, take photos, study them. But
NOBODY can modify, scratch, or overwrite them. "Dekho, par haath
mat lagao!" That's exactly what Readonly<T> does.
Once you create a Readonly<User>, you can read
user.name but you CANNOT do
user.name = "something else". TypeScript puts a
compile-time lock. It's not a runtime lock โ if someone uses
as any or Object.assign, they can still
mutate โ but at the type level, the property is sealed.
Another analogy: The "do not touch" sign at the Salar Jung Museum โ you can admire the musical clock, but woe betide anyone who tries to wind it! Readonly is essential for writing safe, predictable code. When you guarantee that certain data won't change, you eliminate entire categories of bugs. State management, configuration objects, and function arguments all benefit from immutability. TypeScript enforces this at the compiler level, making your intentions clear and preventing accidental mutations that could ripple through your application and cause confusing errors.
Readonly<T> takes a type T and adds
readonly to every property. It uses mapped types
internally to iterate over all keys and apply the modifier.
Basic Usage:
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// { readonly name: string;
// readonly age: number; }
const u: ReadonlyUser = {
name: "Imran",
age: 25
};
// โ ERROR! Cannot assign to
// 'name' because it is read-only
u.name = "Sana";
The Implementation:
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
It maps over every key and adds the readonly modifier.
Real-world use cases:
-
Configuration objects:
const config: Readonly<Config> = { ... }โ prevent accidental modification. -
Function return types:
function getSettings(): Readonly<Settings>โ callers can't modify the returned object. -
Redux state:
Readonly<AppState>โ state should never be mutated directly.
SHALLOW Warning:
const u: Readonly<User> = {
name: "Imran",
address: { city: "Hyd" }
};
// โ ERROR! address is readonly
u.address = { city: "Mumbai" };
// โ
WORKS! Nested object mutates
u.address.city = "Mumbai";
Readonly is SHALLOW โ nested objects can still be mutated! The property itself can't be reassigned, but the nested object's properties can.
Beyond the Readonly<T> utility, TypeScript has
individual readonly modifiers and the powerful
as const assertion.
1. Property-level readonly:
interface User {
readonly id: string;
name: string;
}
// only id is locked, name can change
2. Readonly arrays:
const nums: readonly number[] = [
1, 2, 3
];
// โ ERROR! Can't mutate
nums.push(4);
// Same as ReadonlyArray<number>
3. Readonly tuples:
const pair: readonly [string, number] = [
"hello", 42
];
// โ ERROR! Can't mutate
pair[0] = "world";
4. Const assertion:
const obj = {
name: "Imran",
age: 25
} as const;
// โ ERROR! Cannot assign
obj.name = "Sana";
// typeof obj.name is "Imran"
// NOT string!
as const is the STRONGEST form of readonly โ it
freezes both mutability AND type width. The type becomes the literal
value, and the property becomes readonly.
readonly vs Readonly<T>:
readonly is a keyword for individual properties or
arrays. Readonly<T> is a utility type that
applies readonly to ALL properties of an object type.
Use the keyword for specific locks, and the utility type for locking
the entire object.
Even experienced developers trip over these Readonly gotchas. Let's make sure you avoid every single one of them!
Trap 1: Readonly is SHALLOW.
Readonly<{ address: { city: string } }> only
makes address readonly, not address.city.
For deep readonly, create a DeepReadonly<T>:
type DeepReadonly<T> = {
readonly [K in keyof T]:
T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
Trap 2: Readonly doesn't freeze at runtime.
const u: Readonly<User> = {
name: "Imran"
};
// โ
Works at runtime!
(u as any).name = "Hacked";
TypeScript's readonly is compile-time only. Use
Object.freeze() for runtime immutability:
const u = Object.freeze({ name: "Imran" });
// Silently fails or throws
u.name = "Hacked";
Trap 3: ReadonlyArray assignment.
const readOnly: readonly number[] = [
1, 2
];
// โ ERROR! Can't assign readonly
// to mutable array
const mutable: number[] = readOnly;
This is correct behavior โ otherwise you could mutate through the mutable reference and break the readonly contract!
Trap 4: Readonly in classes.
class X {
readonly name = "fixed";
change() {
// โ ERROR! Even the class
// can't modify readonly
this.name = "new";
}
}
Even the class itself can't modify readonly properties after initialization, except inside the constructor.
Here's your quick-reference cheatsheet for Readonly โ pin it to your mental dashboard and refer back whenever you need it!
-
Utility:
Readonly<T>โ all properties readonly -
Implementation:
{ readonly [K in keyof T]: T[K] } -
Property:
interface X { readonly prop: type } -
Array:
readonly T[]orReadonlyArray<T> -
Tuple:
readonly [A, B] -
Const assertion:
as constโ deepest freeze, literal types -
Shallow:
Readonly<T>is NOT deep -
Deep: Custom
DeepReadonly<T> -
Runtime:
Object.freeze()for real immutability
// Quick reference
interface Config {
host: string;
port: number;
}
// All properties locked
const c: Readonly<Config> = {
host: "localhost",
port: 3000
};
// Deepest lock with literals
const obj = { host: "local" } as const;
Key rules:
- Readonly is compile-time only
- It's SHALLOW by default
- Can't assign readonly to mutable
- Use
as constfor the strongest freeze - Combine with
Object.freezefor runtime
The Golden Rule: Readonly is the Golconda heritage lock โ dekho, par haath mat lagao! But remember bhai, the lock is at the gate only. Inside rooms can still be touched unless you DeepReadonly the whole qila!
Key Takeaways
- Readonly
makes all properties of a type read-only at compile time - Readonly is shallow by default โ nested objects can still be mutated
- Use the readonly keyword for individual properties, arrays, or tuples
- as const provides the deepest freeze, locking mutability and narrowing to literal types
- For runtime immutability, combine Readonly with Object.freeze()
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login