← All concepts
Object-Oriented Programming

Getters and Setters

A getter runs code when a property is read, and a setter runs code when a property is assigned.

Definition

A getter, defined with `get propName()`, and a setter, defined with `set propName(value)`, let a property look like a plain field from the outside while actually running a function underneath. Reading `obj.propName` invokes the getter and uses its return value, no parentheses needed, and writing `obj.propName = value` invokes the setter with `value` as its argument instead of just storing it directly. This makes two things possible that a plain property cannot do. A getter can compute a value on the fly from other data, like a `fullName` getter that joins `first` and `last`, so there is nothing to keep in sync. A setter can validate or transform an incoming value before it is stored, like rejecting a negative age, throwing, or clamping it, so invalid state never gets written to the backing field. By convention the real data is kept in a differently named property, often prefixed with an underscore like `_age`, since a setter named `age` that also tries to write directly to `this.age` would call itself again and overflow the call stack.

Examples

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }
  get fahrenheit() {
    return this.celsius * 9 / 5 + 32;
  }
}
new Temperature(20).fahrenheit; // 68, computed on read, no parentheses

fahrenheit is not stored anywhere, the getter recomputes it from celsius every time it is read.

class Account {
  #balance = 0;
  get balance() {
    return this.#balance;
  }
  set balance(value) {
    if (value < 0) throw new Error("Balance cannot be negative");
    this.#balance = value;
  }
}
const a = new Account();
a.balance = 100; // setter runs, passes validation
a.balance; // 100
a.balance = -5; // throws, #balance stays 100

Assigning to balance routes through the setter, which validates the incoming value before touching the private #balance field, so an invalid assignment never changes the stored value.

const obj = {
  _name: "Ada",
  get name() { return this._name; },
  set name(value) { this._name = value.trim(); },
};
obj.name = "  Grace ";
obj.name; // "Grace", whitespace trimmed by the setter

The setter transforms the incoming string before storing it, and the getter always reads back the already-cleaned value.

Common mistakes

  • Naming a setter the same as the field it writes to, like set age(v) { this.age = v; }, which calls the setter again and overflows the call stack.
  • Forgetting a setter has no return value that matters, whatever it returns is ignored, only the assignment expression's value is used.
  • Adding a heavy computation to a getter and calling it in a loop, since it reruns on every read instead of being cached like a plain field.

Key takeaways

  • get runs when the property is read, set runs when the property is assigned, both without needing parentheses to call them.
  • Getters are useful for values computed from other fields, so there is nothing to keep in sync by hand.
  • Setters are useful for validating or transforming a value before it is stored.
  • The real data usually lives in a differently named or private field so the setter does not call itself.
Check your understanding

class Box { set value(v) { this.value = v; } } const b = new Box(); b.value = 5; What happens?

Where you see this

  • Validating a value, like rejecting a negative age or an empty username, before it is ever stored.
  • Exposing a computed, always-up-to-date value, like a fullName or a total, without a separate field to keep in sync.
  • Normalizing input on write, like trimming whitespace or lowercasing an email, so callers cannot forget to do it.
  • Guarding a private field so external code can only change it through a validated setter, never directly.

Practice this

Further reading

Previous
ES6 Classes
Next
The Event Loop