skip to content

Property Mechanics

Properties are more than key-value pairs: each one carries flags, may be backed by accessor functions, and the whole object can be locked down. This is where interviewers go to find out how libraries build read-only, hidden or computed properties.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

13

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?

level: juniorimportance: must knowfreq 70%

answer

  1. reads and writes run your code
  2. no parentheses at the call site
  3. setter takes exactly one parameter
  4. backing field prevents self-recursion
  5. class accessors live on the prototype

basics

~20 s

get 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

In JavaScript, which property keys does a `for...in` loop visit that `Object.keys(obj)` does not return, and how do you guard a `for...in` loop against them?

level: juniorimportance: must knowfreq 78%

basics

~20 s

for...in walks the prototype chain and visits inherited enumerable string keys as well as own ones; Object.keys returns only the object's own enumerable string keys. Guard each iteration with Object.hasOwn(obj, key), or iterate Object.keys instead.

open as a page

In JavaScript, what does each of Object.preventExtensions(), Object.seal() and Object.freeze() block on an object, and how do the three relate to one another?

level: juniorimportance: must knowfreq 68%

basics

~10 s

Object.preventExtensions blocks adding properties; Object.seal also blocks deleting and reconfiguring them; Object.freeze additionally blocks writing existing values. Each level includes the previous one, and all three are shallow — nested objects stay mutable.

open as a page

In JavaScript, what does `Object.getOwnPropertyDescriptor(obj, key)` return for a data property versus an accessor property, and which attributes can the two kinds never share?

level: middleimportance: must knowfreq 58%

basics

~10 s

A data property's descriptor has value, writable, enumerable and configurable. An accessor property's has get, set, enumerable and configurable. Every property is exactly one kind, so value/writable and get/set can never appear together.

open as a page

Object.freeze() is described as shallow. What does that mean concretely, and how would you implement a deep freeze that handles an object graph containing cycles?

level: middleimportance: must knowfreq 58%

basics

~20 s

Shallow means only the object's own properties are locked; objects those properties point at stay fully mutable. A deep freeze recurses over own keys and freezes each object value, tracking already-visited objects so a cycle does not recurse forever.

open as a page

Why does a property created with `Object.defineProperty(obj, 'id', { value: 1 })` behave differently in JavaScript from the same property created with `obj.id = 1`?

level: middleimportance: should knowfreq 50%

basics

~10 s

Attributes omitted from a defineProperty descriptor default to false, so the property is read-only, non-enumerable and non-configurable. Plain assignment creates it with writable, enumerable and configurable all true.

open as a page

What does `Object.keys({ b: 1, 2: 'two', a: 3, 1: 'one' })` return, and what rule produces that order?

level: middleimportance: should knowfreq 52%

basics

~10 s

It returns ["1", "2", "b", "a"]. Own keys come back in a fixed order: array-index-like integer strings first in ascending numeric order, then the remaining string keys in the order they were created.

open as a page

In JavaScript, how do `Object.keys`, `Object.getOwnPropertyNames`, `Object.getOwnPropertySymbols` and `Reflect.ownKeys` differ in which of an object's own keys they return?

level: middleimportance: should knowfreq 45%

basics

~10 s

Object.keys returns own enumerable string keys; Object.getOwnPropertyNames returns all own string keys including non-enumerable ones; Object.getOwnPropertySymbols returns own symbol keys; Reflect.ownKeys returns every own key, strings then symbols.

open as a page

Assigning to a property of an object returned by Object.freeze() sometimes fails silently and sometimes throws a TypeError. What decides which one happens?

level: middleimportance: should knowfreq 50%

basics

~20 s

Strict mode decides. A failed write to a frozen object's property throws a TypeError in strict-mode code and is silently ignored in sloppy mode. ES modules and class bodies are always strict, so the same line behaves differently across files.

open as a page

A JavaScript object exposes `fullName` through a getter. You copy the object with `{ ...user }` — what does the copy actually contain, and how do you copy it so the accessor survives?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Object spread reads each own enumerable property, so the getter runs once and its result is stored as a plain data property — the copy is a frozen snapshot. Use Object.getOwnPropertyDescriptors to copy descriptors instead.

open as a page

A class instance is passed to `JSON.stringify` and the class's getters and methods are missing from the output, while the constructor-assigned fields are there. Explain the enumerability rule behind that, and how you would get a computed value into the JSON.

level: seniorimportance: should knowfreq 42%

basics

~20 s

JSON.stringify serializes only own enumerable string-keyed properties. Class methods and getters live on the prototype, not the instance, and are non-enumerable, so they are skipped; constructor-assigned fields are own and enumerable. Add a toJSON method to include computed values.

open as a page

A team freezes its shared configuration and lookup objects at startup so that nothing can mutate them at run time. Which parts of that guarantee actually hold, and where does Object.freeze() quietly fail to deliver it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Freezing reliably locks an object's own data properties and prevents new ones or prototype replacement. It does not reach nested objects, Map/Set/Date internals, #private fields, closure state, or accessor setters — and in sloppy-mode code violations fail silently instead of throwing.

open as a page

Once a JavaScript property has been defined with `configurable: false`, what about it can still be changed, and which attempts throw a `TypeError`?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Non-configurable is a one-way door: the property cannot be deleted, reconfigured, or switched between data and accessor. The only permitted changes are on a still-writable data property — its value, and turning writable from true to false.

open as a page