In JavaScript, what do the `get` and `set` keywords do when written inside an object literal or a class body, and how does calling code use the property they create?
answer
- reads and writes run your code
- no parentheses at the call site
- setter takes exactly one parameter
- backing field prevents self-recursion
- class accessors live on the prototype
basics
~20 sget and set define an accessor property: functions the engine runs when code reads or assigns the property with ordinary dot syntax. The getter takes no arguments and returns the value; the setter takes exactly one argument.
solid answer
~50 s`get name() { ... }` and `set name(v) { ... }` create an **accessor property** rather than a stored value. Calling code writes `obj.name` and `obj.name = 'Ada'` exactly as if it were a plain field — no parentheses — and the engine invokes the getter or setter behind that syntax, with `this` bound to the object the property was accessed on. A getter must take zero parameters and return the value; a setter takes exactly one parameter (the assigned value) and its return value is discarded. Typical uses are derived values (`get area() { return this.w * this.h; }`) and validation on write. The classic mistake is writing `this.name = v` inside `set name`, which re-enters the same setter and blows the stack; store into a differently named backing property such as `_name` instead. Declared in a class body, accessors live on the prototype and are shared by all instances.
go deeper
Be able to write a getter and a setter in an object literal and say plainly that calling code uses plain dot syntax with no parentheses. Know that the setter takes one parameter.
Explain that these create an accessor property with get/set instead of value/writable, that class accessors sit on the prototype while literal ones are own properties, and why assigning to the same name inside a setter recurses.
Show the judgment about when a computed property should be an accessor at all: getters must stay cheap and side-effect free because callers cannot see the work hidden behind field syntax, and a throwing getter breaks copying and serialisation.
Own the API-evolution argument — accessors let a stored field become computed, validated or lazily loaded without touching callers, but they also make cost invisible at the call site. Decide where your codebase draws that line and make it a written convention.
## Two kinds of property Every own property of a JavaScript object is exactly one of two kinds. A **data property** holds a value directly. An **accessor property** holds no value at all — it holds up to two functions, a *getter* and a *setter*, and the engine calls them when code reads or writes that property name. The `get` and `set` keywords are the literal-and-class syntax for creating accessor properties. ## Reading through a getter ```js const rect = { width: 3, height: 4, get area() { return this.width * this.height; }, }; console.log(rect.area); // 12 ``` `rect.area` is not a function reference: the engine calls the getter and the expression evaluates to its return value. Writing `rect.area()` would try to *call the result* (`12()`) and throw a `TypeError`. Inside the getter, `this` is the object the property was read on, which is why derived values can reference sibling properties. A getter runs on **every** read. Nothing is cached, so an expensive getter inside a loop is an expensive loop. ## Writing through a setter ```js const user = { _name: '', get name() { return this._name; }, set name(value) { if (typeof value !== 'string') throw new TypeError('name must be a string'); this._name = value.trim(); }, }; user.name = ' Ada '; console.log(user.name); // 'Ada' ``` The setter receives the right-hand side as its single parameter. Its return value is ignored — an assignment expression still evaluates to the assigned value, not to whatever the setter returned. The backing property `_name` matters. If the setter body said `this.name = value`, that assignment would trigger the same setter again, recursing until the engine throws `RangeError: Maximum call stack size exceeded`. The underscore is only a convention; it is a genuinely different property key, which is what breaks the cycle. ## Half-defined accessors You may define only one half: - **Getter only.** Reading works; assigning is a no-op in sloppy mode and throws a `TypeError` in strict mode. Class bodies and ES modules are always strict, so it throws there. - **Setter only.** Assigning works; reading yields `undefined`, because the missing getter is treated as `undefined`. ## Accessors in classes ```js class Temperature { constructor(celsius) { this.celsius = celsius; } get fahrenheit() { return this.celsius * 9 / 5 + 32; } set fahrenheit(f) { this.celsius = (f - 32) * 5 / 9; } } const t = new Temperature(100); console.log(t.fahrenheit); // 212 t.fahrenheit = 32; console.log(t.celsius); // 0 ``` Accessors written in a class body are installed once on `Temperature.prototype`, not copied onto each instance, and each instance reaches them through the prototype chain. Accessors written in an object literal are own properties of that one object. ## How they look as a descriptor The two kinds are visible through `Object.getOwnPropertyDescriptor`: ```js Object.getOwnPropertyDescriptor(rect, 'width'); // { value: 3, writable: true, enumerable: true, configurable: true } Object.getOwnPropertyDescriptor(rect, 'area'); // { get: [Function: get area], set: undefined, enumerable: true, configurable: true } ``` There is no `value` and no `writable` on an accessor property — "can I write to it" is answered by whether a setter exists. Literal accessors come out enumerable and configurable; ones declared on a class prototype are non-enumerable but still configurable and replaceable. The same accessor can be created programmatically: ```js Object.defineProperty(rect, 'perimeter', { get() { return 2 * (this.width + this.height); }, enumerable: true, configurable: true, }); ``` ## What accessors buy and what they cost They buy a stable public surface: a property can start life as a stored field and later become computed, validated, logged, or lazily loaded without a single call site changing. They also let you present a read-only view by supplying a getter and no setter. The cost is that property syntax stops being a promise of cheapness. Code that reads `order.total` has no syntactic hint that a database-shaped computation is hiding there, and a getter that throws will make ordinary-looking reads throw — including reads performed by `JSON.stringify` or by copying an object. Keep getters cheap and free of observable side effects; put anything expensive or fallible behind an explicitly named method instead.
- What happens if code assigns to a property that has a getter but no setter?In sloppy mode the assignment is silently discarded — the read afterwards still returns the getter's value, which makes the bug easy to miss. In strict mode, which includes all class bodies and ES modules, it throws a `TypeError`. Define an explicit setter that throws a clear message if you want a loud failure everywhere.
- Are getter results cached between reads?No — the getter body runs on every single read, so an expensive computation repeats each time. If you want memoisation you have to write it: cache into a backing field on first call, or have the getter replace itself with a plain data property using `Object.defineProperty(this, 'key', { value: computed })` so later reads skip the function entirely.
- How do you define an accessor whose property name is decided at runtime?Use a computed key in the literal or class body — `get [keyName]() { ... }` — where `keyName` is any expression producing a string or symbol. Alternatively call `Object.defineProperty(obj, keyName, { get() { ... }, enumerable: true, configurable: true })`, which is the only option when the object already exists.
saying these in an interview costs you the question
- Says you invoke a getter by writing obj.prop()
- Thinks a setter can declare two parameters
- Writes this.name = v inside set name and expects it to work
- Believes getter results are cached after the first read
- Claims class accessors become own properties of each instance