Skip to main content

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() { ... } and set prop(value) { ... }.
  • From the outside the property looks ordinary: obj.prop and obj.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 configure enumerable, configurable and so on.

Quick example

javascript
const user = { firstName: 'Maria', lastName: 'Kovalenko', get fullName() { return `${this.firstName} ${this.lastName}`; } }; console.log(user.fullName); // "Maria Kovalenko", the getter ran

What 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

javascript
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:

OperationWhat happens
user.fullNamecalls 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

javascript
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 unchanged

Getters 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

javascript
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

ElementWhat it doesSyntax
getterreturns a value on readget prop() { ... }
setterruns on assignmentset prop(value) { ... }
readcalls getobj.prop
writecalls setobj.prop = ...
where it is usedencapsulation, validation, computed valuesYes

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 a RangeError. 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 TypeError under '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 _price for privacy. The underscore is only a naming convention; real privacy comes from private class fields such as #price or 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 ready
Premium

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