Object.isFrozen()
Determines whether an object is frozen — non-extensible with no configurable or writable properties.
Object.isFrozen(obj)- Added in JavaScript ES5 (2009)
Parameters
| Parameter | Type | Description |
|---|---|---|
$obj | Object | The object to check. Non-object arguments are treated as already frozen. |
Return value
booleantrue if the given object is frozen, otherwise false.
Changelog
- PHP ES2015Passing a non-object returns true instead of throwing a TypeError, because primitives are immutable by definition.
Object.isFrozen() reports whether an object satisfies the frozen condition, which is a state test, not a record of whether Object.freeze() was ever called. An object counts as frozen when all three hold: it is not extensible, every own property is non-configurable, and every own data property is non-writable. Accessor properties impose no writability requirement, since they have no [[Writable]] attribute.
When to reach for it
Use it as a guard before mutating an object you did not create, so you can copy instead of writing, and in tests that assert a module really did export immutable data.
What trips people up
An empty object becomes frozen the moment you make it non-extensible — Object.isFrozen(Object.preventExtensions({})) is true, because there are no properties left to violate the rule. Add one writable property first and the same call returns false.
It is also shallow, matching Object.freeze(): a frozen object holding a mutable nested object still reports true. And since ES2015, primitives return true rather than throwing, so a defensive isFrozen(x) check quietly passes for numbers and strings.
Example
When to use it
- Guard a mutation path: copy the object instead of writing to it when it is frozen
- Assert in tests that an exported constants object is genuinely immutable
- Decide whether a caller-supplied options object can be patched in place
- Debug why an assignment silently had no effect
Frequently asked questions
Why does Object.isFrozen() return true for an object I never froze?
Because it tests state, not history: any non-extensible object whose own properties are all non-configurable and non-writable qualifies — including an empty object passed through Object.preventExtensions() or Object.seal().
Does Object.isFrozen() check nested objects?
No — the check is shallow, so an object with frozen own properties reports true even when the objects those properties point to are fully mutable.
What does Object.isFrozen() return for a number or a string?
true, since ES2015 — primitives are immutable by definition, so they are reported as frozen rather than throwing a TypeError as they did in ES5.
More examples
Every integrity level compared
Only the frozen object reports true for isFrozen; sealing stops at non-configurable and leaves values writable.
const open = { a: 1 };
const prevented = Object.preventExtensions({ a: 1 });
const sealed = Object.seal({ a: 1 });
const frozen = Object.freeze({ a: 1 });
for (const [label, o] of Object.entries({ open, prevented, sealed, frozen })) {
console.log(label, Object.isExtensible(o), Object.isSealed(o), Object.isFrozen(o));
}Shallow, like freeze itself
The outer object reports frozen while its nested object is untouched and still mutable — isFrozen never inspects deeper than one level.
const outer = Object.freeze({ inner: { n: 1 } });
console.log(Object.isFrozen(outer));
console.log(Object.isFrozen(outer.inner));
outer.inner.n = 2;
console.log(outer.inner.n);A defensive write helper
The frozen input gets a new patched object while the original keeps mode "read"; the mutable input is patched in place and the same reference comes back.
function withPatch(target, patch) {
return Object.isFrozen(target)
? Object.assign({}, target, patch)
: Object.assign(target, patch);
}
const locked = Object.freeze({ mode: "read" });
const open = { mode: "read" };
console.log(withPatch(locked, { mode: "write" }), locked.mode);
console.log(withPatch(open, { mode: "write" }) === open);
Discussion