Skip to content
TypeScript ts classes 4 min read

Getters & Setters

Getters and setters — collectively called accessors — let a class expose a property whose reads and writes run through methods instead of touching a field directly. From the caller’s perspective the accessor looks like a plain property, but behind the scenes you can compute a value, validate input, or fire side effects. This makes accessors ideal for guarding invariants while keeping a clean, field-like API. TypeScript fully types accessors and, since version 4.3, even lets the getter and setter have different types. This page covers defining accessors, validation, computed values, and how TypeScript checks their types.

Defining a getter

A get accessor is a method declared with the get keyword and no parameters. It is read like a property, not called like a function. Use it to expose a backing field through a public name.

class Temperature {
  private _celsius = 0;

  get celsius(): number {
    return this._celsius;
  }
}

const t = new Temperature();
t.celsius;   // ✅ 0 — no parentheses
t.celsius(); // ❌ Error: This expression is not callable

The convention is to store the underlying value in a private field (here _celsius) and expose it through the accessor. Reading t.celsius invokes the getter transparently.

Defining a setter

A set accessor takes exactly one parameter — the assigned value — and returns nothing. It runs whenever the property is assigned, which is the perfect place to validate or transform input.

class Temperature {
  private _celsius = 0;

  get celsius(): number {
    return this._celsius;
  }

  set celsius(value: number) {
    if (value < -273.15) {
      throw new RangeError("Below absolute zero");
    }
    this._celsius = value;
  }
}

const t = new Temperature();
t.celsius = 25;   // ✅ runs the setter
t.celsius = -300; // ❌ throws RangeError at runtime

Assigning to t.celsius calls the setter, so the validation runs on every write. A property with only a get and no set is read-only from the outside — assigning to it is a compile error.

Computed (derived) getters

Getters need not map to a single field. They can compute a value on demand from other state, giving you a derived property that always stays in sync.

class Rectangle {
  constructor(private width: number, private height: number) {}

  get area(): number {
    return this.width * this.height;
  }

  get isSquare(): boolean {
    return this.width === this.height;
  }
}

const r = new Rectangle(4, 4);
r.area;     // 16
r.isSquare; // true

Because area recomputes each access, it never goes stale. For expensive computations consider caching the result, but beware of invalidation when inputs change.

Different getter and setter types

Since TypeScript 4.3 a setter may accept a wider type than the getter returns. This is useful when you want flexible input but a normalized output.

class Box {
  private _size = 0;

  get size(): number {
    return this._size;
  }

  // Accept number or numeric string, store a number
  set size(value: number | string) {
    this._size = typeof value === "string" ? parseInt(value, 10) : value;
  }
}

const b = new Box();
b.size = "42"; // ✅ setter accepts string
b.size;        // number — getter returns number

The getter type must be assignable to the setter type. Reading always yields number; writing accepts number | string.

Accessors and visibility

Accessors accept the same public, private, protected, and static modifiers as ordinary members. A common pattern is a public getter paired with a private or protected setter to allow internal mutation only.

class User {
  private _name: string;

  constructor(name: string) {
    this._name = name;
  }

  get name(): string {
    return this._name;
  }

  protected set name(value: string) {
    this._name = value.trim();
  }
}
AspectGetter (get)Setter (set)
ParametersNoneExactly one
ReturnThe valuevoid (implicit)
Called likeProperty readProperty assignment
Omit it meansWrite-only propertyRead-only property
Different typeMust be assignable to setterMay be wider than getter

Accessors run real code on every read and write. Keep them cheap and free of surprising side effects — callers expect property access to be fast and predictable.

Best Practices

  • Back accessors with a private field (e.g. _value) to avoid infinite recursion.
  • Use setters to validate or normalize input at the boundary, not deep in your logic.
  • Prefer computed getters over manually kept-in-sync fields to prevent stale data.
  • Keep getters pure and inexpensive; cache only when a computation is genuinely costly.
  • Pair a public getter with a private/protected setter for controlled mutation.
  • Omit the setter entirely to make a property read-only from the outside.
  • Avoid accessors for trivial fields with no logic — a plain public field is simpler.
Last updated June 29, 2026
Was this helpful?