Getters and setters in an object
A getter and a setter are accessor functions bound to a property name: the getter runs on read, the setter runs on assignment. You declare them right inside an object literal with get and set, or add them to an existing object with Object.defineProperty().
Theory
TL;DR
- A getter is a function that is called when a property is read.
- A setter is a function that is called when a value is assigned.
- The literal syntax is
get prop() { ... }andset prop(value) { ... }. - From the outside the property looks ordinary:
obj.propandobj.prop = 5, with no call parentheses. - The main use cases are computed properties, validation of incoming data, and encapsulation of an internal field.
Object.defineProperty()does the same and additionally lets you configureenumerable,configurableand so on.
Quick example
const user = {
firstName: 'Maria',
lastName: 'Kovalenko',
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(user.fullName); // "Maria Kovalenko", the getter ranWhat a getter and a setter are
- A getter is a function that is called when the property is read.
- A setter is a function that is called when a value is assigned to it.
They let you make properties computed, or control exactly what gets written into the object. A getter stores nothing: it recalculates the value from other fields on every read.
The syntax inside an object literal
const user = {
firstName: 'Maria',
lastName: 'Kovalenko',
// getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// setter
set fullName(value) {
const [first, last] = value.split(' ');
this.firstName = first;
this.lastName = last;
}
};
console.log(user.fullName); // "Maria Kovalenko", the getter ran
user.fullName = 'Oleh Petrenko'; // the setter ran
console.log(user.firstName); // "Oleh"
console.log(user.lastName); // "Petrenko"How it works:
| Operation | What happens |
|---|---|
user.fullName | calls the getter and returns its value |
user.fullName = '...' | calls the setter, which handles the assignment |
So fullName looks like a normal property while functions are hidden behind it. A setter always takes exactly one parameter, the value being assigned.
An example with validation
const product = {
_price: 0, // internal storage
get price() {
return this._price;
},
set price(value) {
if (value < 0) {
console.error('Price cannot be negative');
return;
}
this._price = value;
}
};
product.price = 500; // the setter ran
console.log(product.price); // 500, the getter ran
product.price = -10; // rejected, the value is unchangedGetters and setters usually work with "internal" fields whose names start with _. That is a convention, not a language guarantee: product._price is still reachable. When you need real privacy, use private class fields such as #price.
Object.defineProperty(), the alternative way
const user = { firstName: 'Maria', lastName: 'Kovalenko' };
Object.defineProperty(user, 'fullName', {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
const [first, last] = value.split(' ');
this.firstName = first;
this.lastName = last;
}
});
console.log(user.fullName); // "Maria Kovalenko"
user.fullName = 'Oleh Petrenko';
console.log(user.firstName); // "Oleh"This form is more flexible: you can configure the extra attributes (enumerable, configurable and so on). Note that by default Object.defineProperty() makes the property non-enumerable, while an accessor written in a literal is enumerable.
Summary table
| Element | What it does | Syntax |
|---|---|---|
| getter | returns a value on read | get prop() { ... } |
| setter | runs on assignment | set prop(value) { ... } |
| read | calls get | obj.prop |
| write | calls set | obj.prop = ... |
| where it is used | encapsulation, validation, computed values | Yes |
Common mistakes
- Calling an accessor like a method.
user.fullName()throws: the getter has already returned a string, and a string is not a function. - Naming the backing field the same as the accessor.
get price() { return this.price; }recurses forever and ends in aRangeError. The backing field needs a different name, for example_price. - Declaring only a getter and being surprised by a silent assignment. Without a setter, a write is ignored in sloppy mode and throws a
TypeErrorunder'use strict'. - Making a getter expensive. It looks like a plain field read, so a call inside a loop quietly becomes hundreds of computations. Heavy work belongs in a regular method.
- Mistaking
_pricefor privacy. The underscore is only a naming convention; real privacy comes from private class fields such as#priceor from closures. - Forgetting that
JSON.stringify()sees getters. It serialises a getter's result as a normal field, so a computed value can end up in the output JSON.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.