Getters, Setters & Private Fields
Getters and setters let you control how a property is read or written. Private fields hide internal data so the outside world can't accidentally break it.
What Are Getters and Setters?
A getter is a special method that runs when you read a property. A setter is a special method that runs when you write to a property. From the outside, they look just like regular properties β but behind the scenes, code runs automatically.
Think of a thermostat. You can read the temperature (getter) and set a new temperature (setter). But when you "set" it, the thermostat runs its own logic β checking limits, turning on/off the heater, etc. You just see a number; the smart behavior is hidden inside.
Getters β get
Use the get keyword to define a getter. It looks like a method but you access it like a property (no parentheses).
class Circle {
constructor(radius) {
this.radius = radius;
}
get area() {
return Math.PI * this.radius ** 2;
}
get diameter() {
return this.radius * 2;
}
get circumference() {
return 2 * Math.PI * this.radius;
}
}
const c = new Circle(5);
c.area; // 78.54β¦ β reads like a property, runs like a method
c.diameter; // 10
c.circumference; // 31.41β¦
// No parentheses! c.area() would throw an error β it's a getter, not a method
get area() { ... }β defines a getter namedareac.areaβ reading this automatically calls the getter function- The getter returns a computed value β you don't store it separately
- No parentheses when accessing:
c.areanotc.area()
Use a getter when a property is derived from other data β like area from radius, or fullName from firstName and lastName. Instead of storing and manually updating it, compute it fresh each time it's read.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const p = new Person("Alice", "Smith");
p.fullName; // "Alice Smith" β no need to store it separately
p.firstName = "Bob";
p.fullName; // "Bob Smith" β auto-updates because it's computed
Setters β set
Use the set keyword to define a setter. It runs when you assign a value to that property. Setters are perfect for validating values before they're stored.
class Person {
constructor(name, age) {
this.name = name;
this.age = age; // this calls the setter below
}
get age() {
return this._age;
}
set age(value) {
if (typeof value !== "number" || value < 0) {
throw new Error("Age must be a positive number");
}
this._age = value;
}
}
const p = new Person("Alice", 30);
p.age; // 30 β calls the getter
p.age = 31; // calls the setter, validates, stores 31
p.age = -5; // throws Error: "Age must be a positive number"
set age(value) { ... }β runs when you writep.age = somethingthis._ageβ we store the actual value in_age(underscore convention means "internal")- If we stored in
this.ageinside the setter, it would call the setter again β infinite loop! - The getter
get age()reads back fromthis._age - From outside, you just write
p.age = 31β clean and simple, validation is hidden
If you define a get but no matching set, the property is read-only. Trying to assign to it in strict mode throws an error; in non-strict mode the assignment is silently ignored.
class Constant {
get PI() { return 3.14159; }
}
const c = new Constant();
c.PI = 99; // silently ignored (or throws in strict mode)
c.PI; // still 3.14159
Private Fields β #
A private field starts with a # and can only be accessed inside the class. Nothing outside β not even a subclass β can read or write it directly. This is called encapsulation: hiding internal details so they can't be accidentally broken from the outside.
class BankAccount {
#balance = 0; // private β no one outside can touch this directly
#owner; // private β declared here, set in constructor
constructor(owner, initialBalance) {
this.#owner = owner;
this.#balance = initialBalance;
}
deposit(amount) {
if (amount <= 0) throw new Error("Amount must be positive");
this.#balance += amount;
}
withdraw(amount) {
if (amount > this.#balance) throw new Error("Insufficient funds");
this.#balance -= amount;
}
get balance() {
return this.#balance; // read-only access via getter
}
get owner() {
return this.#owner;
}
}
const account = new BankAccount("Alice", 100);
account.deposit(50);
account.balance; // 150 β via getter
account.withdraw(30);
account.balance; // 120
account.#balance; // SyntaxError β cannot access private field from outside
account.#balance = 999999; // SyntaxError β cannot write it either
#balance = 0β declares a private field with a default value of0#ownerβ declares a private field (no default; set in constructor)this.#balance += amountβ accessing private fields inside the class is fineget balance()β exposes the private field as read-only from outside- Trying to do
account.#balanceoutside the class is a SyntaxError β the JS engine rejects it entirely
Before private fields existed, developers used an underscore (_balance) to signal "don't touch this." But it was just a social convention β nothing stopped someone from writing account._balance = 999. Private fields (#balance) are enforced by the JavaScript engine itself β any access from outside throws a SyntaxError.
| _underscore | #privateField | |
|---|---|---|
| Truly private? | No β just a hint | Yes β enforced by JS |
| Access from outside? | Still possible | SyntaxError |
| When added? | Old convention | ES2022 |
Private Methods β #method()
Methods can be private too. Use # before the method name. Private methods can only be called from inside the class.
class Form {
#data = {};
constructor(data) {
this.#data = data;
}
// Private method β internal helper, not part of the public API
#validate() {
if (!this.#data.email) {
throw new Error("Email is required");
}
if (!this.#data.name) {
throw new Error("Name is required");
}
}
submit() {
this.#validate(); // only accessible inside the class
console.log("Form submitted!", this.#data);
}
}
const form = new Form({ name: "Alice", email: "alice@example.com" });
form.submit(); // works β submit() calls #validate() internally
form.#validate(); // SyntaxError β can't call private method from outside
Private Static Fields and Methods
You can combine #private with static:
class IdGenerator {
static #nextId = 1; // private AND static
static generate() {
return IdGenerator.#nextId++;
}
}
IdGenerator.generate(); // 1
IdGenerator.generate(); // 2
IdGenerator.generate(); // 3
IdGenerator.#nextId; // SyntaxError β private even from outside the class
Checking if a Private Field Exists β in operator
Since accessing obj.#field on an object that doesn't have that field throws an error (not just undefined), you can use the in operator to safely check:
class Dog {
#name;
constructor(name) { this.#name = name; }
static isDog(obj) {
return #name in obj; // true if obj has the private #name field
}
}
Dog.isDog(new Dog("Rex")); // true
Dog.isDog({ name: "Rex" }); // false β plain object, no #name
Putting It All Together
Here's a complete example using private fields, getters, setters, and a private method together:
class Temperature {
#celsius; // private storage
constructor(celsius) {
this.celsius = celsius; // calls the setter
}
// Private helper β validates the value
#checkValid(val) {
if (val < -273.15) {
throw new RangeError("Below absolute zero!");
}
}
get celsius() {
return this.#celsius;
}
set celsius(val) {
this.#checkValid(val); // validate first
this.#celsius = val;
}
// Read-only computed property
get fahrenheit() {
return this.#celsius * 9/5 + 32;
}
get kelvin() {
return this.#celsius + 273.15;
}
}
const t = new Temperature(100);
t.celsius; // 100
t.fahrenheit; // 212
t.kelvin; // 373.15
t.celsius = 0;
t.fahrenheit; // 32
t.celsius = -300; // RangeError: Below absolute zero!
t.#celsius; // SyntaxError β private!
Common Mistakes
- Storing in the same name as the getter/setter: If your getter is
get age(), never store inthis.ageinside the setter β it calls the setter again (infinite loop). Use a private field:this.#age. - Calling a getter with parentheses:
c.area()throwsTypeError: c.area is not a function. It's a getter β usec.areawithout parentheses. - Declaring private fields only in the constructor: Private fields must be declared in the class body first (
#field;or#field = value;), not just assigned in the constructor. - Using private fields in a subclass: A private field defined in the parent is truly private β the child class cannot access it. This is intentional.
class Parent {
#secret = 42;
}
class Child extends Parent {
read() {
return this.#secret; // SyntaxError β #secret belongs to Parent only
}
}
Best Practices
- Use private fields (
#field) for any data that should not be changed directly from outside the class. - Use a getter to expose computed or derived values as if they were regular properties.
- Use a setter to validate or transform data before storing it.
- When you have both a getter and setter for the same name, always store the real data in a private field (
#_name) to avoid infinite loops. - Prefer
#privateFieldover the old_underscoreConventionβ it's actually enforced by the language.
Performance Notes
Getters run a function every time the property is read β if the calculation is expensive and you read it often, consider caching the result. Private fields are just as fast as regular properties in modern engines; the privacy check is done at parse time, not at runtime.
Browser Compatibility
Getters and setters (get/set) have been in JavaScript since ES5 and work everywhere. Private fields (#field) were added in ES2022 and are supported in all modern browsers (Chrome 74+, Firefox 90+, Safari 14.1+, Edge 79+). Private methods are ES2022 as well. For older environments, use the _underscore convention as a fallback.
Interview Questions
What is the difference between a getter and a regular method?
Both run code when you access them, but a getter is accessed like a property (no parentheses: obj.name), while a method is called with parentheses (obj.getName()). Getters make the API cleaner when you want something that feels like a property but computes its value dynamically.
Why do you need a private backing field when using getters and setters?
If your getter is named age and you try to store this.age = value inside the setter, it calls the setter again β creating an infinite loop. Storing in a separate private field (this.#age) avoids this: reading this.#age in the getter doesn't trigger the getter again.
What makes private fields (#) better than the underscore convention (_)?
The underscore is just a naming convention β anyone can still access or modify obj._privateField from outside. Private fields with # are enforced by the JavaScript engine itself: accessing obj.#field from outside the class is a SyntaxError, making the data truly inaccessible.
Can a subclass access a parent's private fields?
No. Private fields are truly private to the class they are defined in. Even child classes cannot access them. If a subclass needs to work with that data, the parent must provide a getter, setter, or method.
βοΈ Exercise
Create a Rectangle class with private fields #width and #height. Add getters for both, setters that reject non-positive values, and a read-only getter area that returns width Γ height.
π§ Quiz
1. How do you read a getter? With or without parentheses?
Without parentheses: obj.myGetter, not obj.myGetter().
2. What happens if you try to access a private field from outside the class?
JavaScript throws a SyntaxError β it's not even allowed to compile, unlike a regular property access that would just return undefined.
3. Where must you declare a private field?
At the top of the class body, before the constructor β either as #field; or #field = defaultValue;. You cannot create a private field only inside a constructor without declaring it first.
π Mini Challenge
Build a Password class that stores a password in a private field. Add a setter that throws an error if the password is shorter than 8 characters. Add a getter that returns "β’β’β’β’β’β’β’β’" (always hidden). Add a method verify(attempt) that returns true if the attempt matches the real password.
Summary
- Getters (
get prop()) run a function when you read a property β accessed without parentheses. - Setters (
set prop(value)) run a function when you assign to a property β great for validation. - Always store the value in a private backing field (
#field) when using getters/setters to avoid infinite loops. - Private fields (
#field) are truly inaccessible outside the class β enforced by the JS engine, not just convention. - Private methods (
#method()) work the same way β internal helpers the outside world can't call. - Combine all three to build classes with controlled, safe interfaces.
Related Topics
MDN reference: developer.mozilla.org β JavaScript β Classes β Private class features / get / set