What does readonly do in TypeScript? How far does immutability go?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
readonly prevents reassignment of properties at compile time — readonly props can't be written, arrays become read-only (push/splice blocked). It's a compile-time contract only: nothing changes at runtime, and casting can bypass it.
Detailed explanation
readonly marks properties as non-writable after initialization: interface Point { readonly x: number }. ReadonlyArray<T>/readonly T[] strips mutating methods from the type, and ReadonlyMap/ReadonlySet similarly. Object.freeze pairs it at runtime if enforcement matters. Key limits: readonly is shallow — nested objects stay mutable unless each level declares it; it's erased in emitted JS; and type assertions (as mutable) defeat it deliberately, useful at boundaries. Where it earns its keep: freezing configuration passed into libraries, marking function parameters as non-mutating promises (fn(data: readonly T[]) documents 'I won't touch your array'), and tuple types ([...as const]). Related utilities: Readonly<T>, Immutable patterns via mapped-type recursion. Interview angle: know that readonly protects against accidental writes, not hostile ones — it's documentation the compiler enforces.
Example
interface Config { readonly port: number; tags: readonly string[] }
function audit(config: Config) {
// config.port = 8080; ❌ cannot assign
// config.tags.push('x'); ❌ push doesn't exist on readonly array
console.log(config.tags.join(',')); // reading fine
}
const frozen = Object.freeze({ a: 1 }) as Readonly<{ a: number }>;Interview tip
State the boundary immediately — 'compile-time only, shallow' — then sell the documentation value of readonly parameters. Precision beats breadth here.
Common mistakes
- Believing readonly deep-freezes nested structures.
- Thinking readonly affects the emitted JavaScript.