JavaScript Getters and Setters

Getters and setters are special methods that let you define how properties are accessed and modified. They provide computed properties, validation, and controlled access to your object's data.

Try It Yourself

Experiment with getters and setters to see how they work:

index.js
// Getters and Setters in action

const person = {
  firstName: "John",
  lastName: "Doe",
  birthYear: 1990,

  // Getter - computed property
  get fullName() {
    return this.firstName + " " + this.lastName;
  },

  // Setter - with validation
  set fullName(value) {
    const parts = value.split(" ");
    this.firstName = parts[0] || "";
    this.lastName = parts[1] || "";
  },

  // Getter - calculated value
  get age() {
    return new Date().getFullYear() - this.birthYear;
  }
};

// Using getter (no parentheses!)
console.log("Full name:", person.fullName);
console.log("Age:", person.age);

// Using setter (looks like assignment)
person.fullName = "Jane Smith";
console.log("\nAfter setting fullName:");
console.log("First:", person.firstName);
console.log("Last:", person.lastName);
console.log("Full:", person.fullName);

What are Getters?

A getter is a method that returns a value when you access a property. It's defined with the get keyword and accessed without parentheses:

javascript
const circle = {
  radius: 5,

  // Getter - accessed like a property
  get diameter() {
    return this.radius * 2;
  },

  get area() {
    return Math.PI * this.radius * this.radius;
  },

  get circumference() {
    return 2 * Math.PI * this.radius;
  }
};

// Access getters WITHOUT parentheses
console.log(circle.diameter);      // 10
console.log(circle.area);          // 78.54...
console.log(circle.circumference); // 31.42...

// The getter recalculates when radius changes
circle.radius = 10;
console.log(circle.diameter);      // 20
When to Use Getters
Use getters for computed or derived values that depend on other properties. They recalculate automatically when accessed.

What are Setters?

A setter is a method that runs when you assign a value to a property. It's defined with the set keyword:

javascript
const user = {
  _name: "", // Convention: underscore for "private" property

  // Getter
  get name() {
    return this._name;
  },

  // Setter with validation
  set name(value) {
    if (typeof value !== "string") {
      throw new Error("Name must be a string");
    }
    if (value.length < 2) {
      throw new Error("Name must be at least 2 characters");
    }
    this._name = value.trim();
  }
};

// Setting triggers the setter
user.name = "  Alice  "; // Whitespace is trimmed
console.log(user.name);  // "Alice"

// Validation prevents bad data
try {
  user.name = "A"; // Too short
} catch (e) {
  console.log(e.message); // "Name must be at least 2 characters"
}

Getter and Setter Pairs

Getters and setters often work together to create smart properties:

javascript
const temperature = {
  _celsius: 0,

  // Getter and setter for Celsius
  get celsius() {
    return this._celsius;
  },
  set celsius(value) {
    this._celsius = value;
  },

  // Getter and setter for Fahrenheit (converts automatically)
  get fahrenheit() {
    return this._celsius * 9/5 + 32;
  },
  set fahrenheit(value) {
    this._celsius = (value - 32) * 5/9;
  }
};

// Set in Celsius, read in both
temperature.celsius = 100;
console.log(temperature.celsius);    // 100
console.log(temperature.fahrenheit); // 212

// Set in Fahrenheit, read in both
temperature.fahrenheit = 32;
console.log(temperature.celsius);    // 0
console.log(temperature.fahrenheit); // 32

Getters vs Regular Methods

FeatureGetterRegular Method
Syntax to accessobj.propobj.method()
Parentheses needed
Can accept arguments
Feels like a property
Best forComputed valuesActions/operations

Getters and Setters in Classes

The same syntax works in ES6 classes:

javascript
class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }

  // Getters
  get width() {
    return this._width;
  }

  get height() {
    return this._height;
  }

  get area() {
    return this._width * this._height;
  }

  get perimeter() {
    return 2 * (this._width + this._height);
  }

  // Setters with validation
  set width(value) {
    if (value <= 0) throw new Error("Width must be positive");
    this._width = value;
  }

  set height(value) {
    if (value <= 0) throw new Error("Height must be positive");
    this._height = value;
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.area);      // 50
console.log(rect.perimeter); // 30

rect.width = 20;
console.log(rect.area);      // 100

Using Object.defineProperty

You can also define getters and setters dynamically:

javascript
const obj = {
  _value: 0
};

// Define getter/setter using Object.defineProperty
Object.defineProperty(obj, "value", {
  get: function() {
    console.log("Getting value");
    return this._value;
  },
  set: function(newValue) {
    console.log("Setting value to", newValue);
    this._value = newValue;
  },
  enumerable: true,
  configurable: true
});

obj.value = 42;  // "Setting value to 42"
console.log(obj.value); // "Getting value" then 42

// Define multiple properties at once
Object.defineProperties(obj, {
  doubled: {
    get() { return this._value * 2; }
  },
  squared: {
    get() { return this._value * this._value; }
  }
});

console.log(obj.doubled); // 84
console.log(obj.squared); // 1764

Read-Only Properties

A getter without a setter creates a read-only property:

javascript
const config = {
  _apiKey: "secret-key-123",

  // Read-only property (getter only)
  get apiKey() {
    return this._apiKey;
  }

  // No setter = read-only
};

console.log(config.apiKey); // "secret-key-123"

config.apiKey = "hacked"; // Silently fails (or throws in strict mode)
console.log(config.apiKey); // Still "secret-key-123"

// Creating truly immutable computed properties
const math = {
  get PI() {
    return 3.14159265359;
  },
  get E() {
    return 2.71828182846;
  }
};

console.log(math.PI); // 3.14159265359
math.PI = 3; // Fails silently
console.log(math.PI); // Still 3.14159265359
Strict Mode Behavior
In strict mode, assigning to a getter-only property throws a TypeError. In non-strict mode, it silently fails.

Lazy Evaluation and Caching

Getters can implement lazy evaluation - computing expensive values only when first accessed:

javascript
const data = {
  _cache: null,

  // Expensive computation - only runs once
  get processedData() {
    if (this._cache === null) {
      console.log("Computing (expensive operation)...");
      // Simulate expensive computation
      this._cache = Array.from({ length: 1000 }, (_, i) => i * 2);
    }
    return this._cache;
  },

  clearCache() {
    this._cache = null;
  }
};

// First access - computes
console.log(data.processedData.length); // "Computing..." then 1000

// Second access - uses cache
console.log(data.processedData.length); // 1000 (no "Computing...")

// After clearing cache
data.clearCache();
console.log(data.processedData.length); // "Computing..." then 1000

With Private Fields (ES2022)

Modern JavaScript supports truly private fields with the # prefix:

javascript
// ES2022 private fields with getters/setters

class BankAccount {
  #balance = 0; // Private field

  get balance() {
    return this.#balance;
  }

  // No setter - balance can only change through methods
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }

  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
      return true;
    }
    return false;
  }
}

const account = new BankAccount();
account.deposit(100);
console.log(account.balance); // 100

account.balance = 1000000; // Fails - no setter
console.log(account.balance); // Still 100

// account.#balance = 1000000; // SyntaxError - private field

Test Your Knowledge

Getters and Setters Quiz

5 questions
Question 1

How do you access a getter property?

Question 2

What happens when you assign to a getter-only property?

Question 3

What is the purpose of a setter?

Question 4

Which syntax defines a getter in an object literal?

Question 5

Why use an underscore prefix like _value?

Coding Challenge

Build a Product Class
Medium

Create a Product class with getters and setters for price, quantity, total, and discount. Include validation to prevent invalid values.

Starter Code
// Challenge: Create a Product class with getters and setters
// Requirements:
// - Private _price and _quantity fields
// - Getters for price, quantity, and total (price * quantity)
// - Setters for price and quantity with validation:
//   - Price must be a positive number
//   - Quantity must be a non-negative integer
// - A discount getter/setter that adjusts the price by percentage

class Product {
  constructor(name, price, quantity) {
    // Your code here
  }

  // Add getters and setters
}

// Test your implementation
const laptop = new Product("Laptop", 1000, 5);

console.log("Price:", laptop.price);       // 1000
console.log("Quantity:", laptop.quantity); // 5
console.log("Total:", laptop.total);       // 5000

laptop.price = 1200;
console.log("New total:", laptop.total);   // 6000

laptop.discount = 10; // 10% off
console.log("After discount:", laptop.price); // 1080

// These should throw errors:
// laptop.price = -100;  // Error: must be positive
// laptop.quantity = -1; // Error: must be non-negative

Summary

  • Getters (get) return computed values when a property is accessed
  • Setters (set) run code when a property is assigned
  • Access getters without parentheses like regular properties
  • Use setters for validation and data transformation
  • A getter without a setter creates a read-only property
  • Use underscore prefix (_value) by convention for backing properties
  • ES2022 private fields (#value) provide true privacy