Headshot of Marius Schulz
Marius Schulz Front End Engineer

TypeScript 2.0: Read-Only Properties

In TypeScript 2.0, the readonly modifier was added to the language. Properties marked with readonly can only be assigned to during initialization or from within a constructor of the same class. All other assignments are disallowed.

Let's take a look at an example. Here's a simple Point type that declares two read-only properties, x and y:

type Point = {
  readonly x: number;
  readonly y: number;
};

We can now create an object representing the point (0|0), the origin, and initialize both x and y with the value 0:

const origin: Point = { x: 0, y: 0 };

However, because x and y are marked readonly, we cannot change the value of either property afterwards:

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
origin.x = 100;

A More Realistic Example

While the above example might seem contrived (and it is), consider a function like the following:

function moveX(p: Point, offset: number): Point {
  p.x += offset;
  return p;
}

The moveX function should not modify the x property of the point it was given. Because of the readonly modifier, the TypeScript compiler will yell at you if you try:

Forbidden assignment to readonly property in TypeScript

Instead, moveX should return a new point with updated property values, which could look like this:

function moveX(p: Point, offset: number): Point {
  return {
    x: p.x + offset,
    y: p.y
  };
}

Now the compiler is happy because we're no longer trying to assign a value to a read-only property. We're creating a new point whose properties are initialized with updated values, which is perfectly fine.

Read-Only Class Properties

You can also apply the readonly modifier to properties declared within a class. Here's a Circle class with a read-only radius property and a gettable area property, which is implicitly read-only because there's no setter:

class Circle {
  readonly radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  get area() {
    return Math.PI * this.radius ** 2;
  }
}

Note that the radius is squared using the ES2016 exponentiation operator. Both the radius and the area property can be read from outside the class (because neither one is marked private), but not written to (because both are marked readonly):

const unitCircle = new Circle(1);
unitCircle.radius; // 1
unitCircle.area; // 3.141592653589793

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.radius = 42;

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.area = 42;

Read-Only Index Signatures

Additionally, index signatures can be marked with the readonly modifier. The ReadonlyArray<T> type makes use of such an index signature to prevent assignments to indexed properties:

interface ReadonlyArray<T> {
  readonly length: number;
  // ...
  readonly [n: number]: T;
}

Because of the read-only index signature, the compiler flags the following assignment as invalid:

const primesBelow10: ReadonlyArray<number> = [2, 3, 5, 7];

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
primesBelow10[4] = 11;

readonly vs. Immutability

The readonly modifier is part of TypeScript's type system. It's only used by the compiler to check for illegal property assignments. Once the TypeScript code has been compiled to JavaScript, all notions of readonly are gone. Feel free to play around with this little sample to see how read-only properties are transpiled.

Because readonly is only a compile-time artifact, there's no protection against property assignments at runtime whatsoever. That said, it's another feature of the type system that helps you write correct code by having the compiler check for unintended property assignments from within your TypeScript code base.

This post is part of the TypeScript Evolution series:

  1. TypeScript 2.0: Non-Nullable Types
  2. TypeScript 2.0: Control Flow Based Type Analysis
  3. TypeScript 2.0: Acquiring Type Declaration Files
  4. TypeScript 2.0: Read-Only Properties
  5. TypeScript 2.0: Tagged Union Types
  6. TypeScript 2.0: More Literal Types
  7. TypeScript 2.0: The never Type
  8. TypeScript 2.0: Built-In Type Declarations
  9. TypeScript 2.1: async/await for ES3/ES5
  10. TypeScript 2.1: External Helpers Library
  11. TypeScript 2.1: Object Rest and Spread
  12. TypeScript 2.1: keyof and Lookup Types
  13. TypeScript 2.1: Mapped Types
  14. TypeScript 2.1: Improved Inference for Literal Types
  15. TypeScript 2.1: Literal Type Widening
  16. TypeScript 2.1: Untyped Imports
  17. TypeScript 2.2: The object Type
  18. TypeScript 2.2: Dotted Properties and String Index Signatures
  19. TypeScript 2.2: Null-Checking for Expression Operands
  20. TypeScript 2.2: Mixin Classes
  21. TypeScript 2.3: Generic Parameter Defaults
  22. TypeScript 2.3: The --strict Compiler Option
  23. TypeScript 2.3: Type-Checking JavaScript Files with --checkJs
  24. TypeScript 2.3: Downlevel Iteration for ES3/ES5
  25. TypeScript 2.4: String Enums
  26. TypeScript 2.4: Weak Type Detection
  27. TypeScript 2.4: Spelling Correction
  28. TypeScript 2.4: Dynamic import() Expressions
  29. TypeScript 2.5: Optional catch Binding
  30. TypeScript 2.6: JSX Fragment Syntax
  31. TypeScript 2.7: Numeric Separators
  32. TypeScript 2.7: Strict Property Initialization
  33. TypeScript 2.8: Per-File JSX Factories
  34. TypeScript 2.8: Conditional Types
  35. TypeScript 2.8: Mapped Type Modifiers