Getters, Setters & Property Descriptors

Reviewed & published by Brayan K

Getters and setters are special functions that run when you read or assign a property, letting you compute values and validate input behind plain property syntax — while property descriptors control whether each property is writable, enumerable, and configurable.

Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

They are how you build computed fields, enforce rules on assignment, and lock specific properties without changing how callers use the object.

What You'll Learn in This Lesson

📚 Prerequisites: You should know objects, classes, and the basics of how properties work. Getters and setters extend ordinary property access.

⚙️ Real-World Analogy: A getter/setter is like a thermostat dial:

1️⃣ Getters & Setters in Object Literals

Prefix a function with get or set inside an object literal. A getter runs when you read the property (no parentheses) and is perfect for values derived from other fields. A setter runs when you assign and receives the new value as its single argument — ideal for validation. To callers it all looks like a normal property.

No parentheses: you write person.fullName, not person.fullName(). That's what makes a getter feel like data instead of a method.

const person = {
  first: "Ada",
  last: "Lovelace",
  get fullName() {
    return this.first + " " + this.last;
  },
  set fullName(value) {
    const parts = value.split(" ");
    this.first = parts[0];
    this.last = parts[1];
  },
};

console.log(person.fullName);   // ✅ Expected output: Ada Lovelace

person.fullName = "Grace Hopper"; // setter runs
console.log(person.first);       // ✅ Expected output: Grace
console.log(person.last);        // ✅ Expected output: Hopper

2️⃣ Getters & Setters in Classes (with Validation)

Classes use the same get/set keywords. A common pattern stores the real value in a "private" field (a #name or a leading-underscore convention) and exposes a getter/setter pair that guards it. The setter can throw on bad input, so invalid state never gets stored.

Derived getters stay in sync automatically: fahrenheit recomputes from celsius every time it's read, so there's no stale duplicate value to maintain.

class Temperature {
  #celsius = 0;
  get celsius() { return this.#celsius; }
  set celsius(v) {
    if (v < -273.15) throw new RangeError("below absolute zero");
    this.#celsius = v;
  }
  get fahrenheit() { return this.#celsius * 9 / 5 + 32; }
}

const t = new Temperature();
t.celsius = 25;
console.log(t.celsius);     // ✅ Expected output: 25
console.log(t.fahrenheit);  // ✅ Expected output: 77

try {
  t.celsius = -300;
} catch (e) {
  console.log(e.message);   // ✅ Expected output: below absolute zero
}

3️⃣ Property Descriptors & Object.defineProperty

Every property has a hidden descriptor. Object.defineProperty lets you create or tune a property with explicit flags: writable (can the value change?), enumerable (does it show in loops and Object.keys?), and configurable (can it be deleted or redefined?). Properties defined this way default all three to false. Object.getOwnPropertyDescriptor reveals the current settings.

Normal assignment is generous: a property created with obj.x = 1 is writable, enumerable, and configurable by default. defineProperty flips that to locked-down.

const config = {};

Object.defineProperty(config, "version", {
  value: "1.0",
  writable: false,
  enumerable: false,
});

config.version = "9.9";       // ignored: not writable
console.log(config.version);  // ✅ Expected output: 1.0
console.log(Object.keys(config)); // ✅ Expected output: []

const d = Object.getOwnPropertyDescriptor(config, "version");
console.log(d.writable);      // ✅ Expected output: false
console.log(d.enumerable);    // ✅ Expected output: false

// A normal property is fully open by comparison
config.name = "app";
console.log(Object.getOwnPropertyDescriptor(config, "name").writable); // ✅ Expected output: true

🎯 Your Turn — A Computed Getter

Add a getter area that returns width * height.

const rect = {
  width: 4,
  height: 5,
  // 👉 replace ___ : a getter named area that multiplies the sides
  ___ area() {
    return this.width * this.___;
  },
};

console.log(rect.area);   // ✅ Expected output: 20

🧠 Quick Recall

Predict the output before revealing the answer.

5 — reading o.x runs the getter; note there are no parentheses.

[] — defineProperty defaults enumerable to false.

number — the getter returns the stored number, not a function.

Common Errors & Fixes

🎯 Mini Challenge — Validated Bank Account

Build an account whose balance setter rejects negative values.

class Account {
  #balance = 0;
  get balance() { return this.#balance; }
  set balance(v) {
    if (v < 0) throw new RangeError("balance cannot be negative");
    this.#balance = v;
  }
}

const acc = new Account();
acc.balance = 100;
console.log(acc.balance);     // ✅ Expected output: 100

try {
  acc.balance = -50;
} catch (e) {
  console.log(e.message);     // ✅ Expected output: balance cannot be negative
}
console.log(acc.balance);     // ✅ Expected output: 100

Quick Reference

ConceptMeaning
get x() {}runs on read
set x(v) {}runs on assignment
writablevalue can change
enumerableshows in loops/keys
configurablecan delete/redefine

📚 Keep Learning

🎉 Lesson Complete — Getters, Setters & Descriptors!

Practice quiz

How do you read a getter property named fullName?

  • obj.fullName()
  • obj.get(fullName)
  • obj.fullName
  • obj->fullName

Answer: obj.fullName. A getter is read like a property with no parentheses: obj.fullName.

When does a setter function run?

  • When the property is assigned a value
  • When the property is read
  • When the object is created
  • Only inside a loop

Answer: When the property is assigned a value. A setter runs on assignment and receives the new value as its single argument.

For class Temperature with celsius set to 25, what does the fahrenheit getter (#celsius * 9 / 5 + 32) return?

  • 25
  • 45
  • 98.6
  • 77

Answer: 77. 25 * 9 / 5 + 32 = 45 + 32 = 77.

Object.defineProperty defaults writable, enumerable, and configurable to what?

  • true
  • false
  • undefined
  • null

Answer: false. Properties created with defineProperty default all three flags to false.

After Object.defineProperty(obj, "id", { value: 7 }), what does Object.keys(obj) return?

  • []
  • ["id"]
  • [7]
  • ["value"]

Answer: []. enumerable defaults to false, so the property does not appear in Object.keys.

Which property descriptor flag controls whether a value can be reassigned?

  • enumerable
  • configurable
  • writable
  • readable

Answer: writable. writable controls whether the property's value can be changed.

What does typeof o.n return for const o = { _n: 1, get n() { return this._n; } }?

  • "function"
  • "number"
  • "object"
  • "undefined"

Answer: "number". The getter returns the stored number, so typeof is "number".

Why might get name() { return this.name; } cause an infinite loop?

  • name is a reserved word
  • this is undefined
  • name must be a number
  • The getter calls itself by reading the same property

Answer: The getter calls itself by reading the same property. Reading this.name re-invokes the getter forever; back it with a different field like this._name.

Which function reveals a property's writable/enumerable/configurable flags?

  • Object.keys
  • Object.getOwnPropertyDescriptor
  • Object.freeze
  • Object.assign

Answer: Object.getOwnPropertyDescriptor. Object.getOwnPropertyDescriptor(obj, key) returns the property's descriptor.

If an object has a getter for x but no setter, what happens when you do obj.x = 5?

  • It throws immediately in all modes
  • It creates a new property
  • The assignment is ignored (no setter)
  • It runs the getter

Answer: The assignment is ignored (no setter). Without a matching setter, the assignment is ignored.

Continue this course

Frequently asked questions

What is the difference between a getter and a regular method?

A getter is accessed like a property — obj.fullName, with no parentheses — but runs a function behind the scenes each time you read it. A method is called with parentheses — obj.getFullName(). Getters are perfect for computed values that should feel like plain data.

Why use a setter instead of just assigning a property?

A setter intercepts assignment (obj.age = 5) so you can validate, transform, or log the incoming value before storing it. That lets you reject invalid data or keep derived fields in sync, all while callers use ordinary assignment syntax.

What are the three property descriptor flags?

writable controls whether the value can be reassigned, enumerable controls whether the property shows up in loops and Object.keys, and configurable controls whether the property can be deleted or its descriptor changed. Properties created with Object.defineProperty default all three to false.

How do I inspect a property's descriptor?

Call Object.getOwnPropertyDescriptor(obj, 'key') to get an object describing that property — its value (or get/set functions) plus the writable, enumerable, and configurable flags. Object.getOwnPropertyDescriptors returns descriptors for every own property at once.