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();
}
}
| Aspect | Getter (get) | Setter (set) |
|---|---|---|
| Parameters | None | Exactly one |
| Return | The value | void (implicit) |
| Called like | Property read | Property assignment |
| Omit it means | Write-only property | Read-only property |
| Different type | Must be assignable to setter | May 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
privatefield (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/protectedsetter 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
publicfield is simpler.