Readonly Properties in TypeScript

Readonly marks TypeScript object properties as immutable at compile time. Add the modifier before a property name, and TypeScript prevents reassignment. Learn the syntax and its limits.

5 min read

TypeScript readonly properties cannot be reassigned after the object is created. You mark one with the readonly modifier before the property name. TypeScript enforces this at compile time, but the restriction does not exist at runtime.

Use readonly when you want to make it clear that a property should be set once and never changed. It is a signal of intent, backed by the compiler.

Marking a property as readonly

Add the readonly keyword before the property name in any object type. After creation, trying to change a readonly property produces a compile error:

typescripttypescript
interface Point {
  readonly x: number;
  readonly y: number;
}
 
const origin: Point = { x: 0, y: 0 };
origin.x = 5;
// Error: Cannot assign to 'x' because it is a read-only property.

You can still read readonly properties freely. The restriction only applies to assignment on the left side of an equals sign.

Readonly vs const

The rule is simple: use const for variables, readonly for properties.

ModifierWhere usedWhat it prevents
constVariable declarationsReassigning the variable to a new value
readonlyObject propertiesReassigning the property on an existing object

A variable declared with const cannot be pointed at a new value. A property marked readonly cannot be reassigned once the object exists. They work at different levels but serve the same purpose: catching unintended mutations.

Readonly is not deep

The readonly modifier only protects the property it is directly on. If that property holds an object, the nested object's own properties are still mutable.

typescripttypescript
interface Home {
  readonly resident: { name: string; age: number };
}
 
const home: Home = { resident: { name: "Ada", age: 30 } };
home.resident.age += 1; // OK -- nested properties are not readonly

The age property is not marked readonly, so updating it is allowed. But you cannot replace the entire resident object with a new one, because the resident property itself is readonly.

This is a frequent source of confusion. Readonly is shallow by design. If you need deep immutability, you must mark every nested property as readonly, or build a deep-readonly mapped type. See mapped types in TypeScript for more on that technique.

Readonly arrays

Arrays have a special readonly variant. The ReadonlyArray type removes all mutating methods so the compiler rejects them.

typescripttypescript
function sum(values: readonly number[]) {
  let total = 0;
  for (const v of values) total += v;
  return total;
}
 
console.log(sum([1, 2, 3])); // 6

This prints 6. You can pass a regular mutable array to a function that expects a readonly array. This is safe because the function promises not to mutate. The reverse is not allowed: you cannot pass a readonly array to a function that expects a mutable one, because that function might call push or splice on it.

Unlike Array, there is no ReadonlyArray constructor. Create a regular array and assign it to a readonly-typed variable instead.

Readonly and type compatibility

TypeScript does not consider readonly when checking whether two types are compatible. A readonly object can be assigned to a mutable variable of the same shape.

typescripttypescript
interface Person { name: string; age: number; }
interface ReadonlyPerson { readonly name: string; readonly age: number; }
 
let writable: Person = { name: "Ada", age: 30 };
let ro: ReadonlyPerson = writable;
writable.age += 1;
console.log(ro.age); // 31

The ro variable is typed as ReadonlyPerson, so you cannot assign to its properties directly. But the same object is also referenced by writable, which can mutate it. The readonly view does not prevent the underlying object from changing through a different reference. This means readonly is a best-effort compile-time guard, not a security guarantee.

When to use readonly

Use readonly when a property should be set during creation and never changed -- like an ID or a creation timestamp. Also use it for function parameters that should not be mutated by the function body, especially arrays. For related patterns, see readonly values in TypeScript.

Do not use readonly when the object is meant to change freely or when you need runtime enforcement of immutability. For runtime enforcement, use Object.freeze.

Common mistakes

Expecting readonly to be deep. Marking a property readonly only protects reassignment of that exact property. Nested objects inside it are still mutable. Repeat readonly at every level you want to protect.

Expecting readonly to prevent mutation at runtime. Readonly is erased. If another piece of code has a mutable reference to the same object, it can change the value. Readonly is a development-time signal, not a runtime lock.

Using const where readonly belongs, or vice versa. Variables get const. Properties get readonly. There is no const for object properties and no readonly for standalone variables.

Rune AI

Rune AI

Key Insights

  • Add readonly before a property name to prevent reassignment at compile time.
  • Readonly is not deep: nested objects inside a readonly property can still be mutated.
  • Use readonly on properties and const on variables -- they serve similar roles at different levels.
  • ReadonlyArray prevents mutating array methods but still lets you read elements.
  • Readonly objects can be assigned to mutable variables; readonly does not affect type compatibility.
RunePowered by Rune AI

Frequently Asked Questions

Does readonly prevent mutation at runtime?

No. Readonly is erased during compilation. At runtime, the property can be changed like any other. Readonly is a compile-time signal that helps you and your team avoid accidental reassignments during development.

Can I use readonly on arrays?

Yes. Use ReadonlyArray or the shorthand `readonly T[]`. These types remove mutating methods like push, pop, and splice. The compiler prevents you from calling those methods.

How do I remove readonly from a type?

Use a mapped type with the minus-readonly modifier: `type Mutable<T> = { -readonly [K in keyof T]: T[K] }`. This strips readonly from all properties.

Conclusion

Readonly marks a property as immutable at compile time. It does not change runtime behavior and does not make nested objects deeply immutable. Use it to signal intent: this property is set once and should not change. Think of it as documentation the compiler can enforce.