Once a JavaScript property has been defined with `configurable: false`, what about it can still be changed, and which attempts throw a `TypeError`?
answer
- one-way door, never reopened
- deletion is off the table
- narrowing is allowed, widening is not
- writable true to false, once
- identical redefinition is a legal no-op
basics
~20 sNon-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.
solid answer
~40 s`configurable: false` locks the property record. You can no longer `delete` it, flip `configurable` back to `true`, change `enumerable`, convert a data property into an accessor or back, or replace an existing `get`/`set` function — each of those throws `TypeError: Cannot redefine property` from `Object.defineProperty`. Two changes remain legal, and only for a data property that is still `writable: true`: its `value` may be updated, and `writable` may be turned from `true` to `false` — a one-way narrowing that cannot be reversed. Once the property is both non-configurable and non-writable, the only `Object.defineProperty` call that succeeds is one restating the identical value, which the spec accepts as a no-op via a SameValue comparison. `delete` on a non-configurable property returns `false` in sloppy mode and throws a `TypeError` in strict mode.
go deeper
Know that configurable: false means the property cannot be deleted and its flags cannot be changed afterwards — it is a decision you cannot take back.
List what throws (reopening configurable, changing enumerable, switching between data and accessor) and the two changes that remain legal on a still-writable data property.
Explain the invariant the rules protect — a locked property must report a stable value and never vanish, so engines and proxies may cache it — and choose writable: false over configurable: false when you still want an escape hatch.
Treat irreversibility as an API commitment: pinning a property forecloses future refactoring for every consumer, including your own team, so decide deliberately which invariants deserve a lock and document the ones that do.
## Why `configurable` is the important flag `writable` governs one operation — assignment. `configurable` governs the property's whole existence: whether it can be removed and whether its descriptor may ever be rewritten. Because turning it off cannot be undone, it is the attribute you set last and most deliberately. ```js const o = {}; Object.defineProperty(o, 'id', { value: 1, writable: true, enumerable: true, configurable: false, }); ``` ## What now throws Every one of these raises `TypeError: Cannot redefine property: id`: ```js Object.defineProperty(o, 'id', { configurable: true }); // cannot reopen Object.defineProperty(o, 'id', { enumerable: false }); // cannot hide it Object.defineProperty(o, 'id', { get() { return 2; } }); // cannot become an accessor ``` And deletion fails: ```js delete o.id; // sloppy mode: evaluates to false, property remains // strict mode: TypeError ``` The reverse conversion is barred too: a non-configurable **accessor** property cannot become a data property, and its `get` or `set` functions cannot be swapped for different functions. Supplying the very same function references is accepted as a no-op, since the spec compares them with SameValue rather than rejecting any redefinition outright. ## What is still allowed Exactly two changes survive, and only on a data property that is still writable: ```js o.id = 42; // ordinary write: fine Object.defineProperty(o, 'id', { value: 43 }); // also fine Object.defineProperty(o, 'id', { writable: false }); // true -> false: fine, and final ``` The `writable: true -> false` transition is legal because it only ever narrows what the property permits; a program that could already observe the value can never be surprised by the property becoming more restrictive. Going back — `writable: false -> true` on a non-configurable property — throws, because that would widen a guarantee other code may already rely on. After that narrowing, the property is fully sealed. The only `Object.defineProperty` call that still succeeds is one that restates every attribute exactly as it already is: ```js Object.defineProperty(o, 'id', { value: 43 }); // no-op, succeeds (SameValue match) Object.defineProperty(o, 'id', { value: 44 }); // TypeError ``` Note the SameValue detail: `NaN` is considered equal to `NaN`, so restating a `NaN` value succeeds, while `+0` and `-0` are considered different and restating one in place of the other throws. ## The invariant behind the rules All of this exists to protect one guarantee that the language, and any `Proxy` or engine optimisation built on top of it, may rely on: **a non-configurable, non-writable data property will report the same value forever, and a non-configurable property will never disappear.** Once code has observed those facts it may cache them. If reconfiguration were permitted, that cache could go stale and observable behaviour would become inconsistent. Every allowed transition is one that cannot break the guarantee; every banned transition is one that could. ## Reading the state before you act Because the failures are all-or-nothing, defensive code checks first: ```js const d = Object.getOwnPropertyDescriptor(o, 'id'); if (d && d.configurable) { Object.defineProperty(o, 'id', { enumerable: false }); } ``` `Reflect.defineProperty` performs the same operation but returns a boolean instead of throwing, which suits code that wants to attempt a redefinition and branch on the outcome rather than wrap it in `try`. ## Where you meet this in practice Several common situations produce non-configurable properties without anyone typing the attribute: - `Object.defineProperty` with a partial descriptor, since omitted attributes default to `false` on creation; - a global variable declared with `var` at the top level of a script, which becomes a non-configurable property of the global object (unlike a plain assignment, which is configurable); - library code that deliberately pins an identifier so plugins cannot shadow it. The practical advice is to treat `configurable: false` as irreversible and to reach for it only when you genuinely mean "this property, with this shape, for the lifetime of this object". If you merely want a value that should not be reassigned, `writable: false` alone leaves you the ability to reconfigure later — you keep the escape hatch and still get the read-only behaviour.
- Can a non-configurable property ever be made read-only after the fact?Yes, if it is still a data property with `writable: true`. Turning `writable` from `true` to `false` is the one permitted attribute change, because it only narrows what the property allows. The reverse — reopening a non-configurable, non-writable property for writing — throws a `TypeError`, so the transition is strictly one-way.
- What does `delete obj.key` evaluate to when the property is non-configurable?`false` in sloppy mode: the operator reports failure and the property stays. In strict mode the same expression throws a `TypeError` instead. That mode split is a frequent source of confusion, because deletion appears to be silently ignored in one file and fatal in another.
- How do you attempt a redefinition without risking an exception?Use `Reflect.defineProperty(obj, key, descriptor)`, which performs the identical operation but returns `true` or `false` rather than throwing, so you can branch on the result. Alternatively read `Object.getOwnPropertyDescriptor` first and check the `configurable` flag before calling `Object.defineProperty`.
saying these in an interview costs you the question
- Thinks configurable can be flipped back to true
- Says a non-configurable property can still be deleted in sloppy mode
- Believes writable false can be reopened to true
- Confuses configurable with writable and expects assignments to fail
- Assumes any redefinition of a locked property throws, even an identical one