JavaScript Property Descriptors - Hidden Property Settings

What they are: Every object property has secret settings that control how it behaves beyond just its value.

The 3 Hidden Flags Every Property Has:

javascript

let user = { name: "John" };
 
// Check the hidden settings
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
console.log(descriptor);
/*
{
  value: "John",
  writable: true,    // Can you change the value?
  enumerable: true,  // Shows up in for...in loops?
  configurable: true // Can you delete it or change these flags?
}
*/

Default behavior: When you create properties normally, all flags are true.

writable - Can You Change the Value?

javascript

let user = { name: "John" };
 
// Make it read-only
Object.defineProperty(user, "name", {
    writable: false
});
 
user.name = "Pete"; // Error! Cannot assign to read only property
console.log(user.name); // Still "John"

Use case: Creating constants that can’t be accidentally changed.

enumerable - Shows Up in Loops?

javascript

let user = {
    name: "John",
    age: 30
};
 
// Hide age from loops
Object.defineProperty(user, "age", {
    enumerable: false
});
 
for (let key in user) {
    console.log(key); // Only "name" - age is hidden
}
 
console.log(Object.keys(user)); // ["name"] - age missing
console.log(user.age); // 30 - still accessible directly

Use case: Hide internal properties from iteration.

configurable - Can You Delete or Modify Flags?

javascript

let user = { name: "John" };
 
// Lock down the property
Object.defineProperty(user, "name", {
    configurable: false
});
 
delete user.name; // Error! Cannot delete
Object.defineProperty(user, "name", { writable: false }); // Error! Cannot modify flags

Use case: Permanent protection - once set to false, you can never change it back.

Common Patterns:

Pattern 1: Create Read-Only Property

javascript

let user = {};
 
Object.defineProperty(user, "id", {
    value: 123,
    writable: false,    // Can't change
    enumerable: true,   // Shows in loops
    configurable: false // Can't delete or modify
});
 
user.id = 456; // Silently fails (error in strict mode)
console.log(user.id); // 123

Pattern 2: Hide Internal Properties

javascript

let user = {
    name: "John",
    _internal: "secret"
};
 
// Hide the internal property
Object.defineProperty(user, "_internal", {
    enumerable: false
});
 
for (let key in user) {
    console.log(key); // Only "name"
}

Pattern 3: Constants Like Math.PI

javascript

let config = {};
 
Object.defineProperty(config, "API_URL", {
    value: "https://api.example.com",
    writable: false,
    enumerable: true,
    configurable: false
});
 
// Now it's like Math.PI - completely locked

Creating Multiple Properties at Once:

javascript

let user = {};
 
Object.defineProperties(user, {
    name: {
        value: "John",
        writable: true,
        enumerable: true
    },
    age: {
        value: 30,
        writable: false,    // Read-only age
        enumerable: false   // Hidden from loops
    }
});

Interview Gotchas:

1. Default flags for defineProperty are false:

javascript

let user = {};
 
// Normal way - all flags true
user.name = "John";
 
// defineProperty way - all flags false by default!
Object.defineProperty(user, "age", {
    value: 30
    // writable: false, enumerable: false, configurable: false
});

2. configurable:false is permanent:

javascript

let user = { name: "John" };
 
Object.defineProperty(user, "name", { configurable: false });
 
// This is now IMPOSSIBLE:
Object.defineProperty(user, "name", { configurable: true }); // Error!
Object.defineProperty(user, "name", { enumerable: false }); // Error!
delete user.name; // Error!

3. One exception - writable can go from true to false:

javascript

let user = { name: "John" };
 
Object.defineProperty(user, "name", { configurable: false });
 
// This still works:
Object.defineProperty(user, "name", { writable: false }); // OK
// But can't go back:
Object.defineProperty(user, "name", { writable: true }); // Error!

Practical Uses:

1. Library constants:

javascript

const MyLibrary = {};
 
Object.defineProperty(MyLibrary, "VERSION", {
    value: "1.0.0",
    writable: false,
    configurable: false
});

2. Hide utility methods:

javascript

let obj = {
    data: "important",
    _helper() { return "internal"; }
};
 
Object.defineProperty(obj, "_helper", { enumerable: false });

3. Clone with exact same flags:

javascript

let original = { name: "John" };
Object.defineProperty(original, "name", { writable: false });
 
// Copy with same flags
let clone = Object.defineProperties({}, 
    Object.getOwnPropertyDescriptors(original)
);

Quick Rules:

  1. writable: false = Read-only value
  2. enumerable: false = Hidden from loops/Object.keys
  3. configurable: false = Can’t delete or change flags (permanent!)
  4. defineProperty defaults to false for all flags
  5. configurable: false is one-way - can never undo it

Bottom line: Property descriptors give you fine control over how properties behave. Most useful for creating constants, hiding internals, and preventing accidental modifications.