skip to content

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%

answer

  1. a property is a record of attributes
  2. two kinds, never both at once
  3. value and writable versus get and set
  4. enumerable and configurable are shared
  5. own properties only, not inherited

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.

solid answer

~50 s

`Object.getOwnPropertyDescriptor` returns a plain object describing one **own** property, or `undefined` if the object has no own property under that key — it does not search the prototype chain. For a data property you get `{ value, writable, enumerable, configurable }`; for an accessor property you get `{ get, set, enumerable, configurable }`, where a missing half shows up as `undefined`. `enumerable` and `configurable` are the two attributes both kinds share: `enumerable` controls whether key-listing operations see it, `configurable` controls whether the property can be deleted or its descriptor changed. The two kinds are mutually exclusive by definition, so passing both `value` and `get` to `Object.defineProperty` throws a `TypeError` — an accessor has no `writable` attribute because writability is expressed by whether a setter exists. `Object.getOwnPropertyDescriptors` (plural) returns a map of every own key's descriptor, symbol keys included.

go deeper

for a junior

Know that a property is more than a value — it carries flags — and that Object.getOwnPropertyDescriptor shows them. Recognise value/writable as the stored-value shape.

for a middle

Recite both descriptor shapes accurately, explain that enumerable and configurable are the shared pair, and say why an accessor has no writable attribute and why mixing the groups throws.

for a senior

Use descriptors as a diagnostic tool: when a property will not copy, will not delete, or will not appear in a listing, read its descriptor before guessing, and know the read-modify-defineProperty round trip for changing one attribute.

for a principal

Frame descriptors as the contract layer of an object API — what you expose as configurable is what future code may reshape. Decide deliberately which library-defined properties are locked down and document why.

## Properties are records, not just values In the specification an object property is not simply a name bound to a value: it is a record of **attributes**. Reflecting that record into ordinary JavaScript is what `Object.getOwnPropertyDescriptor(obj, key)` does. The shape of what comes back tells you which of the two possible kinds the property is. ## The data-property descriptor ```js const o = { count: 1 }; Object.getOwnPropertyDescriptor(o, 'count'); // { value: 1, writable: true, enumerable: true, configurable: true } ``` - **value** — the stored value, returned as-is on every read. - **writable** — whether assignment may replace `value`. When `false`, assignment is discarded in sloppy mode and throws a `TypeError` in strict mode. - **enumerable** — whether the key is visible to key-listing operations. - **configurable** — whether the property may be deleted, and whether its other attributes may be changed later. ## The accessor-property descriptor ```js const rect = { w: 3, h: 4, get area() { return this.w * this.h; } }; Object.getOwnPropertyDescriptor(rect, 'area'); // { get: [Function: get area], set: undefined, enumerable: true, configurable: true } ``` - **get** — a function called on read, or `undefined`. - **set** — a function called on write with one argument, or `undefined`. - **enumerable**, **configurable** — identical in meaning to the data case. Notice what is *absent*: there is no `value` and no `writable`. An accessor stores nothing, so there is nothing for `value` to hold, and "can this be written" is answered by whether `set` is a function. A getter-only property is the accessor world's read-only. ## The kinds are mutually exclusive Mixing the two field groups is an error, not a merge: ```js Object.defineProperty({}, 'x', { value: 1, get() { return 2; } }); // TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute ``` The same rule bans `writable` alongside `get`/`set`. A property can, however, *change* kind over its lifetime — redefining a configurable data property with `{ get }` converts it to an accessor and drops `value`/`writable`; the reverse conversion works the same way. If the property is non-configurable, both conversions throw. ## Own only, never inherited ```js class A { get id() { return 1; } } const a = new A(); Object.getOwnPropertyDescriptor(a, 'id'); // undefined Object.getOwnPropertyDescriptor(Object.getPrototypeOf(a), 'id'); // { get, set: undefined, ... } ``` The `undefined` result surprises people who expect it to mirror a plain read. Because class accessors and methods live on the prototype, you must look at `A.prototype` — or walk the chain with `Object.getPrototypeOf` in a loop — to find their descriptors. Note also that the prototype descriptor reports `enumerable: false`: class-body members are deliberately non-enumerable, while object-literal members are enumerable. ## The plural form ```js Object.getOwnPropertyDescriptors(rect); // { w: {...}, h: {...}, area: { get: f, set: undefined, ... } } ``` `Object.getOwnPropertyDescriptors(obj)` (ES2017) returns one object mapping every own key — strings *and* symbols — to its descriptor. It pairs with `Object.defineProperties(target, descriptorMap)` and with `Object.create(proto, descriptorMap)`, both of which consume exactly that shape, which is what makes descriptor-preserving object construction possible. ## Reading a returned descriptor is safe The returned object is a fresh, ordinary object each call — a snapshot, not a live view. Mutating it changes nothing about the real property; you must feed it back through `Object.defineProperty` for an edit to take effect: ```js const d = Object.getOwnPropertyDescriptor(o, 'count'); d.writable = false; // no effect yet Object.defineProperty(o, 'count', d); // now the property is read-only ``` That round-trip is the idiomatic way to change one attribute while preserving the rest, and it is why descriptors are a *record you can pass around* rather than a hidden engine detail. ## Why interviewers ask this The descriptor is the vocabulary underneath a whole family of behaviours: why a property never appears in a key listing, why an assignment silently does nothing, why a library-defined property refuses to be deleted, and why copying an object with spread turns a getter into a frozen snapshot. A candidate who can name the two shapes and say which attributes they share can explain all of those from first principles instead of memorising each symptom.

  • Why does `Object.getOwnPropertyDescriptor(instance, 'someMethod')` return `undefined` for a class method?
    Because class methods and accessors are installed on `ClassName.prototype`, not on each instance, and this API inspects **own** properties only. Look the descriptor up on the prototype — `Object.getOwnPropertyDescriptor(Object.getPrototypeOf(instance), 'someMethod')` — or walk the chain with `Object.getPrototypeOf` until you find the key. You will also see `enumerable: false` there, since class-body members are non-enumerable by design.
  • Can a property change from a data property into an accessor after it has been created?
    Yes, provided it is still `configurable: true`. Calling `Object.defineProperty` with a `get` and/or `set` converts it: the old `value` and `writable` attributes are dropped, and the accessor's `enumerable`/`configurable` default to `false` unless you restate them. If the property is non-configurable the conversion throws a `TypeError` in either direction.

saying these in an interview costs you the question

  • Says an accessor property has a writable attribute
  • Expects the descriptor of an inherited or prototype property
  • Passes both value and get to defineProperty and expects a merge
  • Thinks the returned descriptor is a live view you can edit in place
  • Believes every property must store a value somewhere

context