Skip to main content

Getters and setters in a class

A getter and a setter are a pair of special class methods that run automatically on a read (obj.prop) and on an assignment (obj.prop = value). From the outside the property looks like an ordinary field, but there is code behind it that can validate, compute or log.

Theory

TL;DR

  • get name() fires when obj.name is read, set name(value) fires when obj.name = value is assigned.
  • It is an accessor property: no value is stored in it, the value is computed or delegated to another field.
  • Getter only means a read only property; setter only means a write only property (reading returns undefined).
  • They pair perfectly with #field private data: the data is protected and the access is controlled.
  • The same mechanism is available outside classes through Object.defineProperty or an object literal.

Quick example

javascript
class User { constructor(name) { this._name = name; // a "private" field by convention } get name() { console.log('getter called'); return this._name; } set name(value) { console.log('setter called'); if (value.length < 2) throw new Error('Name is too short'); this._name = value; } } const u = new User('Maria'); console.log(u.name); // 'getter called', then 'Maria' u.name = 'Oleh'; // 'setter called' console.log(u.name); // 'Oleh'

The call syntax stays exactly like a plain field: no parentheses, no getName().

How it works under the hood

get and set in a class body are shorthand for an accessor property descriptor. Formally the class does roughly what Object.defineProperty does:

javascript
const user = { _name: 'Maria' }; Object.defineProperty(user, 'name', { get() { return this._name.toUpperCase(); }, set(value) { this._name = value.trim(); } }); user.name = ' oleh '; console.log(user.name); // 'OLEH'

Important consequences:

  • The accessor lives on the class prototype, not on the instance, so it is not duplicated in every object.
  • It is non enumerable (enumerable: false), so it does not appear in Object.keys(instance) or in the JSON.stringify(instance) output. If a computed value must be in the JSON, add a toJSON() method.
  • The descriptor can be inspected: Object.getOwnPropertyDescriptor(User.prototype, 'name') returns an object with get and set fields.
  • An accessor can be static: static get version() { return '1.0'; } is read as User.version.

Getter without a setter, and setter without a getter

With only a getter defined, the property becomes read only. It is typically used for computed values: area, fullName, priceWithTax.

javascript
class Circle { constructor(radius) { this.radius = radius; } get area() { return Math.PI * this.radius ** 2; } } const c = new Circle(10); console.log(c.area); // 314.159... c.area = 100; // in a module or in strict mode: TypeError console.log(c.area); // 314.159..., the value did not change

In sloppy mode such an assignment is silently ignored, while in modules and in strict mode it throws TypeError: Cannot set property area. Class code is always strict, but the assignment line itself runs in its own context, so the behaviour depends on that context.

The mirror case, a setter without a getter, is handy when you only need to react to a write and recompute internal data.

javascript
class Temperature { set celsius(value) { this.fahrenheit = value * 1.8 + 32; } } const t = new Temperature(); t.celsius = 25; console.log(t.fahrenheit); // 77 console.log(t.celsius); // undefined, there is no getter

Working with private fields

Most often a getter and a setter are a facade over a #field private property. The data is unreachable from outside, but the class decides how to expose it and what to accept.

javascript
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 acc = new Account(); acc.balance = 100; console.log(acc.balance); // 100 // acc.balance = -50; // Error: Balance cannot be negative

This gives you the main benefit of accessors: the public contract (acc.balance) stays stable while the internal storage can change at any time.

Computed properties

A getter together with a setter lets you split a composite value into parts and assemble it back.

javascript
class User { constructor(first, last) { this.first = first; this.last = last; } get fullName() { return `${this.first} ${this.last}`; } set fullName(value) { const [first, last] = value.split(' '); this.first = first; this.last = last; } } const u = new User('Maria', 'Koval'); console.log(u.fullName); // 'Maria Koval' u.fullName = 'Oleh Shevchenko'; console.log(u.first); // 'Oleh' console.log(u.last); // 'Shevchenko'

The fullName property does not exist as a field in memory: it is recomputed from first and last every time, so the data can never drift out of sync.

Comparison with a plain property

FeaturePlain propertyGetter or setter
Stores a valueyesno, it computes or delegates
Runs code on accessnoyes, automatically
Can add validation or logicnoyes
Works with private fieldsyesyes
Access syntaxobj.propobj.prop, identical
Lives on the instanceyeson the class prototype
Included in JSON.stringifyyesno

Common mistakes

  • Calling it like a method. u.name() throws TypeError: u.name is not a function, because a getter returns a value, not a function.
  • Infinite recursion. Writing this.name = value inside set name(value) makes the setter call itself and blow the stack. You need separate storage: this._name or #name.
  • Same name for the accessor and a field. get value() together with a class field value = 1 does not work: the instance field shadows the prototype accessor.
  • Expecting accessors in JSON. Computed properties are not serialised, because they are non enumerable and live on the prototype.
  • Heavy computation in a getter. Reading a property looks cheap, so a call inside a loop or a render easily becomes a bottleneck; cache the result.
  • Side effects in a getter. Network requests, state mutations and logging make the code unpredictable: a getter should only return a value.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.