Read-only properties
In JavaScript a property can be made read-only, meaning it cannot be changed, deleted or redefined. There are several ways to do it, from configuring a single property to freezing the entire object.
Theory
TL;DR
Object.defineProperty(obj, key, { writable: false })makes one specific property read-only.- Adding
configurable: falsealso forbids deleting it or redefining its descriptor. Object.freeze(obj)freezes the whole object: no changes, no additions, no deletions.Object.seal(obj)is weaker: values can change, the object's shape cannot.- A getter without a setter gives you a computed read-only property.
- In sloppy mode a failed write is ignored silently, in strict mode it throws
TypeError.
Quick example
const user = {};
Object.defineProperty(user, 'name', {
value: 'Alice',
writable: false, // forbids changing the value
enumerable: true, // the property shows up when iterating
configurable: false // cannot be deleted or redefined
});
console.log(user.name); // "Alice"
user.name = 'Bob'; // does not change the value
console.log(user.name); // "Alice"
delete user.name; // does not delete the property
console.log(user.name); // "Alice"Object.defineProperty
This is the main way to define a property with exactly the attributes you want. writable: false makes it read-only, and configurable: false additionally prevents deletion and any further redefinition through defineProperty.
You can inspect the attributes with the descriptor:
console.log(Object.getOwnPropertyDescriptor(user, 'name'));It prints:
{
value: 'Alice',
writable: false,
enumerable: true,
configurable: false
}Note that when you create a property with Object.defineProperty() and omit the attributes, they all default to false. So the property ends up read-only whether you meant it or not.
Object.freeze
When every property has to become immutable at once, freeze the object:
const user = {
name: 'Alice',
age: 25
};
Object.freeze(user);
user.name = 'Bob'; // does not change the value
user.city = 'Kyiv'; // cannot add a new property
delete user.age; // cannot delete an existing one
console.log(user); // { name: 'Alice', age: 25 }Object.freeze() does two things:
- every existing property gets
writable: falseandconfigurable: false; - the object becomes non-extensible, so new properties cannot be added.
Freezing is shallow: nested objects stay mutable, and user.profile.city = 'Lviv' still works. A deep freeze has to walk the object graph recursively.
A getter without a setter
For a computed value you do not need a descriptor at all: a property with get but no set is read-only by definition.
const user = {
firstName: 'Alice',
lastName: 'Smith',
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(user.fullName); // "Alice Smith"
user.fullName = 'Bob Jones'; // has no effectObject.seal
When a full freeze is too much and you only want to lock the shape of the object:
const user = { name: 'Alice' };
Object.seal(user);
user.name = 'Bob'; // allowed
delete user.name; // not allowed
user.age = 30; // not allowedUnlike freeze(), seal() lets you change the values of existing properties but forbids changing the set of keys.
Comparison
| Approach | What it does | Change a value | Add or delete |
|---|---|---|---|
Object.defineProperty(obj, key, { writable: false }) | Makes one property read-only | No | Yes |
Object.freeze(obj) | Freezes the whole object | No | No |
Object.seal(obj) | Forbids adding and deleting properties | Yes | No |
| Getter without a setter | Computed read-only property | No | Yes |
In one line: to make a property read-only use Object.defineProperty(obj, 'key', { value: val, writable: false }), or freeze the whole object with Object.freeze(obj).
Common mistakes
- Expecting an exception in sloppy mode. Outside strict mode, assigning to a read-only property simply does nothing and raises no error, so the bug is easy to miss. In modules and classes, where strict mode is always on, the same code throws
TypeError. - Confusing
freezewithseal.seal()does not make properties read-only, it only locks the set of keys. - Assuming
Object.freeze()is deep. Nested objects and arrays remain fully mutable after the parent is frozen. - Forgetting
configurable: false. Withwritable: falsebutconfigurable: truethe property can be redefined by anotherObject.defineProperty()call, which defeats the protection. - Leaving attributes implicit. In
Object.defineProperty()an omittedwritable,enumerableorconfigurabledefaults tofalse, so the property may unexpectedly disappear fromObject.keys()and from loops.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.