How to use get
and set
for clean APIs, validation, caching, and more—with real-world patterns in JavaScript and TypeScript.

get
and set
for clean APIs, validation, caching, and more—with real-world patterns in JavaScript and TypeScript.Introduction
Most devs think of object properties as just “bags of values”:
const user = { name: "Ali" };
console.log(user.name); // "Ali"
But JavaScript lets you define properties that run code when you access or assign them. These are getters and setters.
They’re powerful for things like:
- Computed values (
fullName
fromfirstName
+lastName
) - Validation (reject invalid assignments)
- Lazy evaluation (compute once, cache forever)
- Deprecation shims (warn when old fields are used)
- Encapsulation (control how data is exposed)
In this post, you’ll learn how to use them effectively, common mistakes, and practical recipes you can use in real-world apps.
1. Declaring Getters and Setters
The syntax is built right into object literals and classes:
const user = {
firstName: "Ali",
lastName: "Khan",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
const [first, ...rest] = value.split(" ");
this.firstName = first;
this.lastName = rest.join(" ");
}
};
console.log(user.fullName); // "Ali Khan"
user.fullName = "Laiba Rahmat";
console.log(user.firstName); // "Laiba"
- Getter (
get
) runs when you read the property. - Setter (
set
) runs when you assign to the property.
2. Defining with Object.defineProperty
For finer control over descriptors:
const account = {};
let _balance = 0;
Object.defineProperty(account, "balance", {
get() {
return _balance;
},
set(v) {
if (v < 0) throw new Error("Balance cannot be negative");
_balance = v;
},
enumerable: true,
configurable: true
});
account.balance = 100;
console.log(account.balance); // 100
👉 Useful when you want to hide backing fields or make them non-enumerable.
3. Real-World Use Cases
A) Computed Properties
const rect = {
width: 10,
height: 5,
get area() {
return this.width * this.height;
}
};
console.log(rect.area); // 50
No need to manually recalc — area
is always derived from current state.
B) Validation on Assignment
const user = {
_age: 0,
get age() {
return this._age;
},
set age(value) {
if (value < 0) throw new Error("Age must be positive");
this._age = value;
}
};
user.age = 25; // ok
// user.age = -5; // throws
C) Encapsulation & Hidden State
class Counter {
#count = 0; // private field (ES2022+)
get value() {
return this.#count;
}
set value(v) {
if (!Number.isInteger(v)) throw new Error("Must be integer");
this.#count = v;
}
}
const c = new Counter();
c.value = 5;
console.log(c.value); // 5
D) Lazy Evaluation + Caching
const data = {
get expensive() {
console.log("computing...");
const value = Math.random();
Object.defineProperty(this, "expensive", { value, writable: false });
return value;
}
};
console.log(data.expensive); // logs "computing..." then number
console.log(data.expensive); // cached number, no recompute
E) Deprecation Shims
const settings = {
theme: "dark",
get colour() {
console.warn("⚠️ 'colour' is deprecated, use 'theme' instead.");
return this.theme;
},
set colour(v) {
console.warn("⚠️ 'colour' is deprecated, use 'theme' instead.");
this.theme = v;
}
};
settings.colour = "light"; // warning
console.log(settings.theme); // "light"
4. Getters/Setters in Classes
Widely used in OOP-style JavaScript (e.g., with TypeScript).
class User {
constructor(private _email: string) {}
get email() {
return this._email.toLowerCase();
}
set email(v: string) {
if (!v.includes("@")) throw new Error("Invalid email");
this._email = v;
}
}
const u = new User("Test@Example.com");
console.log(u.email); // "test@example.com"
u.email = "new@example.com"; // ok
👉 TypeScript automatically infers types for get
/set
pairs.
5. Integration with JSON, Spread, and Keys
- Enumerable matters: by default, getters/setters are enumerable in object literals.
- Non-enumerable props won’t appear in:
Object.keys(obj)
for...in
loopsJSON.stringify(obj)
const obj = {
get hidden() { return 42; }
};
console.log(Object.keys(obj)); // []
console.log(JSON.stringify(obj)); // {}
👉 If you want getters in JSON, return them explicitly in .toJSON()
:
const user = {
first: "Ali",
last: "Rahmat",
get fullName() {
return `${this.first} ${this.last}`;
},
toJSON() {
return { fullName: this.fullName };
}
};
console.log(JSON.stringify(user)); // {"fullName":"Ali Rahmat"}
6. Gotchas to Watch Out For
- Don’t abuse for heavy computation
- Getters should feel cheap. If expensive, cache the result.
2. Avoid side effects in getters
- A
get
should not cause mutations or network calls. Treat them like pure functions.
3. One descriptor = either data or accessor
- You can’t have
value
+get
in the same property.
4. Don’t forget enumerable/configurable flags
- Especially when using
Object.defineProperty
, defaults arefalse
.
5. Beware infinite recursion
const obj = {
get value() {
return this.value; // ❌ calls itself forever
}
};
7. Handy Utility Patterns
Freeze computed values once
function defineCached(obj, key, compute) {
Object.defineProperty(obj, key, {
get() {
const val = compute();
Object.defineProperty(obj, key, { value: val });
return val;
},
configurable: true
});
}
Define multiple props at once
Object.defineProperties(user, {
fullName: {
get() { return `${this.first} ${this.last}`; },
enumerable: true
},
initials: {
get() { return `${this.first[0]}${this.last[0]}`; },
enumerable: true
}
});
8. Performance Considerations
- Getters/setters are slightly slower than direct property access, but negligible unless in hot loops.
- Use caching for expensive calculations.
- For interception-heavy logic (like observing every property), use a Proxy instead.
Conclusion
Getters and setters are a powerful middle ground between plain objects and full-blown classes:
- ✅ Getters: computed, derived, or lazy properties.
- ✅ Setters: validate or transform inputs.
- ✅ Work seamlessly in objects, classes, and
defineProperty
. - ⚠️ Don’t abuse for side effects or expensive work — keep them predictable.
Pro Tip: Think of them as controlled views of data: getters expose data in a safe, readable way; setters protect your object from invalid state.
Call to Action (CTA)
Have you used getters/setters for something clever — like caching or API wrappers?
Share your snippet in the comments, and pass this article to a teammate who still thinks get
/set
are “just syntactic sugar.”
Leave a Reply