cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Property flags & descriptors

Learn how JavaScript property descriptors control writable, enumerable, and configurable behavior, and how definition differs from assignment.

By the end, you can
  • 01
    Read the hidden settings panelUse getOwnPropertyDescriptor and getOwnPropertyDescriptors to inspect real property flags.
  • 02
    Install properties deliberatelyUse Object.defineProperty, Object.defineProperties, and Object.create descriptor maps.
  • 03
    Predict blocked operationsTell when assignment, deletion, enumeration, and redefinition succeed or fail.

Every property has switches

You already use object properties constantly: user.name, settings.theme, cart.total. Until now, a property may have looked like just a name and a value. JavaScript keeps a little more information than that. A property descriptor is the complete settings panel for one property.

This lesson is about data descriptors, the kind that hold a value. They have the value plus three flags: writable, enumerable, and configurable. The next lesson, Getters & setters, swaps the value box for get and set functions. That is called an accessor descriptor.

Real-life analogyA hidden settings panel

Imagine every property has a tiny settings panel behind the wall. Turning writable off makes the value read-only. Turning enumerable off makes it hidden from normal lists. Turning configurable off locks the panel itself.

In real life: A light switch marked writable
In JavaScript: Can assignment change the value?
In real life: A display switch marked enumerable
In JavaScript: Does the property show up in listings?
In real life: A maintenance switch marked configurable
In JavaScript: Can the switches be changed, or the door removed?

Where the analogy stops: The panel is not a real object you can edit directly. JavaScript exposes a copy through descriptor methods, and you ask the engine to install changes.

The three data-property flags
FlagWhen trueWhen false
writableAssignment can change the value of a data property.Assignment cannot change the value. In strict code it throws TypeError; in sloppy code it silently leaves the value alone.
enumerableThe property appears in Object.keys, for...in, object spread, and JSON.stringify.The property is still readable by name, but normal listings skip it.
configurableThe property can be deleted and most descriptor switches can be changed.The property cannot be deleted or reconfigured. The one allowed tightening is writable: true to false.

These flags explain why Math.PI cannot be overwritten, why some properties are invisible to Object.keys, and why libraries can expose a stable public API while keeping internal slots out of loops and JSON output.

Object.defineProperty installs the settings panel

INTERACTIVE

Ordinary assignment goes through a property’s normal door: obj.x = 1. Definition is stronger. Object.defineProperty installs or rewrites the property rules directly. It takes the object, the property name, and a descriptor object.

One descriptor objectPop out in the code editor (opens in a new tab)JavaScript
"use strict";
const account = {};

Object.defineProperty(account, "id", {
  value: 42,
  writable: false,
  enumerable: true,
  configurable: false,
});

console.log(account.id); // 42

The code says: install an id property with value 42, make it visible in listings, but do not allow assignment to replace it or a later descriptor to loosen the rules. Because CompleteFrontend’s site code is strict, a failed write throws a TypeError. In sloppy scripts, the same failed write silently leaves the old value.

Descriptor inspector
Strict code under testPop out in the code editor (opens in a new tab)JavaScript
"use strict";const panel = {};Object.defineProperty(panel, "choice", {  value: "open",  writable: true,  enumerable: true,  configurable: true,}); panel.choice = "changed";delete panel.choice;Object.keys(panel);JSON.stringify(panel);
Real outcomedescriptor
  • Before{"value":"open","writable":true,"enumerable":true,"configurable":true}
    assignchanged
    deletetrue
    keys(none)
    json{}
    redefineok
    After{"writable":false,"enumerable":false,"configurable":false}
Try it yourself
Switches:Try:

Fresh object each time. With writable=true, enumerable=true, configurable=true: assign: changed

Toggle the three descriptor switches. The playground recreates a fresh object for each combination so every operation shows its real strict-mode result.

Try all three switches. Notice the surprise: enumerable does not control whether you can read panel.choice. It controls whether listing tools see it. configurable is the lock that affects deletion and most redefinition.

getOwnPropertyDescriptor(s): inspect the real panel

STEP THROUGH

JavaScript gives you two inspection tools. Use Object.getOwnPropertyDescriptor(obj, name) for one own property. Use Object.getOwnPropertyDescriptors(obj) for every own property at once. They do not walk the prototype chain.

Compare literal and defineProperty defaults
Step 0 of 4Ready
Your turn: follow the blue line

Step through two ways to create a property, then compare the real descriptors JavaScript reports.

Running in
  1. script
Next: line 2
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
"use strict";Object.defineProperty(normal, "quiet", { value: 2 }); console.log(Object.getOwnPropertyDescriptor(normal, "visible"));console.log(Object.getOwnPropertyDescriptor(normal, "quiet"));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

A built-in property is a useful reality check. In current JavaScript, Math.PI is a data property whose descriptor says writable: false, enumerable: false, and configurable: false. That is why strict code cannot replace it, Object.keys(Math) does not show it, and you cannot delete it.

Inspecting a built-inPop out in the code editor (opens in a new tab)JavaScript
const pi = Object.getOwnPropertyDescriptor(Math, "PI");
console.log(pi.value);
console.log(pi.writable);
console.log(pi.configurable);
Own property means not inherited

getOwnPropertyDescriptor answers only for properties stored directly on that object. If a value comes from a prototype, inspect the prototype object or use your prototype knowledge from Object.create to climb the chain deliberately.

Default flags: assignment is friendly, definition is strict

The most important gotcha is that different creation paths choose different defaults. Object literal syntax and assignment create normal everyday properties: writable, enumerable, and configurable are all true. Object.defineProperty is the opposite: every flag you omit defaults to false.

Creation path defaults
How the property is createdDefault flags
Object literal: { x: 1 }writable: true, enumerable: true, configurable: true
Assignment: obj.x = 1Creates a normal own property with all three flags true, if assignment is allowed.
Object.defineProperty(obj, "x", { value: 1 })Every omitted flag is false: read-only, hidden, and non-configurable.

This is deliberate. Definition is a low-level tool. If JavaScript guessed permissive flags while you were defining a property, it would be easy to accidentally expose or loosen an internal detail.

Strict vs sloppy failed writesPop out in the code editor (opens in a new tab)JavaScript
const sloppy = {};
Object.defineProperty(sloppy, "x", { value: 1, writable: false });
sloppy.x = 2;
console.log(sloppy.x);

(function strictMode() {
  "use strict";
  const strict = {};
  Object.defineProperty(strict, "x", { value: 1, writable: false });
  try { strict.x = 2; } catch (error) { console.log(error.name); }
})();

The first half is a sloppy script: assigning to a non-writable property does not throw, and the value remains 1. The second half is strict, so the same write is reported as TypeError. The course uses strict modules, so the strict behavior is the one you should expect in lesson code.

Defining many properties

STEP THROUGH

A descriptor map is an object whose keys are property names and whose values are descriptor objects. Two APIs use that shape: Object.defineProperties adds many descriptors to an existing object, and Object.create can create an object with descriptors in one step.

Define several properties from descriptor maps
Step 0 of 5Ready
Your turn: follow the blue line

Follow a descriptor map through Object.create, Object.defineProperties, Object.keys, and full inspection.

Running in
  1. script
Next: line 2
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
"use strict";  id: { value: 42, enumerable: true },  role: { value: "admin", enumerable: true },}); Object.defineProperties(user, {  token: { value: "abc", writable: false },  label: { value: "Ada", writable: true, enumerable: true },}); console.log(Object.keys(user));console.log(Object.getOwnPropertyDescriptors(user));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

A common use is creating visible public fields and hidden metadata together. In the walkthrough, token exists and can be read directly, but because enumerable was omitted it is skipped by Object.keys and JSON.stringify.

The same descriptor map shapePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const user = Object.create(null, {
  id: { value: 42, enumerable: true },
  role: { value: "admin", enumerable: true },
});

Object.defineProperties(user, {
  token: { value: "abc", writable: false },
  label: { value: "Ada", writable: true, enumerable: true },
});

console.log(Object.keys(user));
console.log(Object.getOwnPropertyDescriptors(user));

Assignment vs definition

STEP THROUGH

Assignment and definition can look like two ways to “put a property on an object,” but they obey different rules. Assignment uses the normal property access machinery. It can call inherited setters and it respects inherited non-writable data properties. Definition writes an own descriptor directly onto the target object.

Real-life analogyWriting on a notebook page

Assignment follows the first instruction JavaScript finds. If the prototype has a setter, that setter runs. defineProperty instead adds an own labeled property directly to the target object.

In real life: Writing on the page normally
In JavaScript: Assignment: child.name = "Ada"
In real life: Following a note on an earlier page
In JavaScript: An inherited setter that runs during assignment
In real life: Adding a new labeled page
In JavaScript: Definition: Object.defineProperty(child, "name", ...)

Where the analogy stops: The notebook image is only about how a property is created. JavaScript still enforces descriptor rules.

Assignment walks in; definition rewires
Step 0 of 9Ready
Your turn: follow the blue line

Compare normal assignment with direct property definition.

Running in
  1. script
Next: line 3
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
"use strict";const log = [];  set name(value) { log.push("setter:" + value); }};Object.defineProperty(proto, "locked", { value: 1, writable: false }); const child = Object.create(proto);child.name = "Ada";Object.defineProperty(child, "name", { value: "Ada", enumerable: true }); try { child.locked = 2; } catch (error) { log.push(error.name); }Object.defineProperty(child, "locked", { value: 2, enumerable: true }); console.log(log);console.log(child.name, child.locked);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Assignment and definition side by sidePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const log = [];
const proto = {
  set name(value) { log.push("setter:" + value); }
};
Object.defineProperty(proto, "locked", { value: 1, writable: false });

const child = Object.create(proto);
child.name = "Ada";
Object.defineProperty(child, "name", { value: "Ada", enumerable: true });

try { child.locked = 2; } catch (error) { log.push(error.name); }
Object.defineProperty(child, "locked", { value: 2, enumerable: true });

console.log(log);
console.log(child.name, child.locked);

This distinction matters when you work with objects created from prototypes, class instances, or library objects. If you want to trigger the public API, assign. If you need to install a precise descriptor, define.

Where you’ll use this

Descriptors are not something you use for every property. They are a professional tool for edges where “normal property” is too broad.

  • Read-only public constants. A library can expose version information or a token name without letting consumers overwrite it by accident.
  • Hidden metadata. Frameworks often store internal markers that should not appear in Object.keys, object spread, or JSON output.
  • Compatibility wrappers. You can install a property now with a shape that a later getter or setter can replace when the next lesson’s accessor descriptors become useful.
  • Testing and debugging. Inspecting descriptors helps explain why a write, loop, or delete behaves differently from a plain object literal.
Allowed or blocked?
  • Assign to a property with writable: true
  • Assign to an own data property with writable: false in strict code
  • Read obj.secret when secret has enumerable: false
  • See a non-enumerable property in Object.keys(obj)
  • Delete a property with configurable: true
  • Change configurable: false back to true
  • Change writable: true to false on a non-configurable property
  • Use defineProperty to create an own property where a prototype has a setter
Try it yourself
0 of 8 correct

Sort each operation by what the descriptor rules allow. Assume strict code when assignment fails.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Misconceptions and one-way locks

“Non-enumerable means private.”

No. It is hidden from common listings, but anyone who knows the name can read it, and getOwnPropertyDescriptors can reveal it.

“writable false freezes the whole object.”

It locks one data property’s value. Other properties can still be added or changed unless other rules stop them. Freezing and sealing come next in this module.

“configurable false means the value can never change.”

If writable is still true, assignment can still change the value. Configurability controls deletion and descriptor reconfiguration.

“You can unlock a property later.”

configurable: false is one-way. You can tighten a non-configurable writable data property from writable: true to false, but you cannot turn it back on.

“Assignment and defineProperty are interchangeable.”

Assignment can trigger setters and can be blocked by inherited read-only properties. Definition creates or changes an own descriptor directly.

The one-way rule

Once a property is non-configurable, JavaScript protects the shape other code may be relying on. The allowed change from writable: true to false only makes the property stricter; it never loosens the contract.

Practice: property descriptors

5 EXERCISES
Exercise 1 · Warm-upMake a read-only property

Read the starter code and predict the two console lines. This is the smallest useful descriptor: a value plus flags.

Starter codePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const profile = {};
Object.defineProperty(profile, "id", {
  value: 101,
  writable: false,
  enumerable: true,
  configurable: true,
});
try { profile.id = 202; } catch (error) { console.log(error.name); }
console.log(profile.id);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict operations with omitted flags

    Work out the three console lines without running the code.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    "use strict";
    const user = {};
    Object.defineProperty(user, "token", { value: "abc" });
    console.log(Object.keys(user).length);
    console.log(user.token);
    try { delete user.token; } catch (error) { console.log(error.name); }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeInspect a built-in’s descriptor

      Use Object.getOwnPropertyDescriptor to inspect a built-in. What two booleans print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const descriptor = Object.getOwnPropertyDescriptor(Math, "PI");
      console.log(descriptor.writable);
      console.log(descriptor.configurable);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeHide a property from Object.keys

        Which property name does Object.keys(settings) show?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const settings = { theme: "dark" };
        Object.defineProperty(settings, "token", {
          value: "abc",
          enumerable: false,
        });
        console.log(Object.keys(settings));
        console.log(settings.token);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain assignment vs definition with an inherited setter

          What does the inherited setter log, and why does the definition line not log again?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          "use strict";
          const log = [];
          const proto = { set name(value) { log.push(value); } };
          const child = Object.create(proto);
          child.name = "Ada";
          Object.defineProperty(child, "name", { value: "Own Ada", enumerable: true });
          console.log(log.join(","));
          console.log(child.name);

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your descriptor instincts

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Which call returns the descriptor for one own property?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the descriptor inspection print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const box = { x: 1 };
              console.log(Object.getOwnPropertyDescriptor(box, "x").writable);

              Choose an answer to see the explanation.

            3. Question 3 of 7What does Object.keys report for a hidden property?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const box = {};
              Object.defineProperty(box, "x", { value: 1 });
              console.log(Object.keys(box).length);

              Choose an answer to see the explanation.

            4. Question 4 of 7Which flag decides whether delete obj.x may remove a property?

              Choose an answer to see the explanation.

            5. Question 5 of 7What prints when strict assignment hits an inherited read-only property?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "use strict";
              const parent = {};
              Object.defineProperty(parent, "x", { value: 1, writable: false });
              const child = Object.create(parent);
              try { child.x = 2; } catch (error) { console.log(error.name); }

              Choose an answer to see the explanation.

            6. Question 6 of 7Which statement about configurable: false is correct?

              Choose an answer to see the explanation.

            7. Question 7 of 7What prints when definition bypasses an inherited setter?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const log = [];
              const proto = { set name(value) { log.push(value); } };
              const child = Object.create(proto);
              Object.defineProperty(child, "name", { value: "Ada" });
              console.log(log.length);

              Choose an answer to see the explanation.

            Key takeaways

            • A property descriptor is the value plus hidden flags for an own property.
            • writable controls assignment to a data property’s value.
            • enumerable controls whether common listing tools see the property.
            • configurable controls deletion and most future descriptor changes.
            • Literal and assignment defaults are permissive; defineProperty defaults omitted flags to false.
            • Assignment uses normal property access. Definition installs an own descriptor directly.

            One-liner.
            A descriptor is the hidden settings panel that tells JavaScript whether a property can be changed, listed, deleted, or reconfigured.

            Up next: Getters & setters.

            CompleteFrontend Clear concepts. Working examples.