Lesson 43 of 80
Properties that run code when read or written, and property names built from a variable instead of typed literally — two small syntaxes that make objects behave less like plain data bags.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A normal property just stores a value. A getter (get) runs a function every time the property is read, and a setter (set) runs a function every time it's assigned. From the outside, the caller still writes obj.total or obj.total = 5 — plain property syntax — but underneath, your code is intercepting every access.
Computed property names are the separate, unrelated-sounding feature that solves "I don't know the property's name until runtime." Wrapping an expression in [] inside an object or class literal evaluates that expression to produce the key:
const key = "score";
const obj = { [key]: 100 }; // { score: 100 }
They show up together constantly because both let an object's shape react to something other than a hardcoded literal.
class Temperature {
#celsius = 0;
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set fahrenheit(value) {
if (typeof value !== "number") {
throw new TypeError("fahrenheit must be a number");
}
this.#celsius = (value - 32) * 5 / 9;
}
get celsius() {
return this.#celsius;
}
}
const t = new Temperature();
t.fahrenheit = 98.6; // runs the setter — validates, then converts and stores
console.log(t.celsius); // 37 — computed by the getter, not stored directly
Nobody calls t.setFahrenheit(98.6) — it looks exactly like assigning a plain property, t.fahrenheit = 98.6. But every write goes through validation, and every read of fahrenheit is computed fresh from the one real field, #celsius, rather than kept in sync by hand. There is no fahrenheit field at all — it's a live computation, and a getter/setter pair is what makes it look like a stored value from the outside.
Object literals support the same pair without a class:
const user = {
firstName: "Ada",
lastName: "Lovelace",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(" ");
},
};
user.fullName; // "Ada Lovelace"
user.fullName = "Grace Hopper";
user.firstName; // "Grace"
Now combine computed property names with an object where the key itself needs to be dynamic — say, building a lookup object from a form field's name at runtime:
function makeFieldError(fieldName, message) {
return { [fieldName]: message };
}
makeFieldError("email", "invalid format"); // { email: "invalid format" }
Object.keys(), for...in, and destructuring all treat them as ordinary enumerable properties, not as methods. The syntax difference is invisible from outside the class.{[key.toUpperCase()]: value} or even {[Math.random()]: value} are both legal. This is exactly what lets libraries build objects keyed by things only known at runtime, like a Redux reducer keyed by action type.get/set syntax in an object or class literal is sugar for calling Object.defineProperty with get/set functions in its descriptor instead of a plain value. Understanding this syntax first makes the next lesson's fine-grained property control — writable, enumerable, configurable — read as a natural extension rather than a new topic.