Object.freeze()
ES5+Freezes an object in place and returns it: no properties can be added, removed, or reassigned, and existing property descriptors cannot be reconfigured. The freeze is shallow — nested objects referenced by the frozen object remain fully mutable unless frozen themselves.
Syntax
Object.freeze(obj)Parameters
obj Object The object to freeze
Return Value
The frozen object
Examples
const obj = { name: 'John' };
Object.freeze(obj);
obj.name = 'Jane';
obj.age = 30;
console.log(obj); 📌 When to Use
Use Object.freeze() to make intent explicit: this object is a constant, and anything trying to change it is a bug. Freeze lookup tables, enum-like maps of allowed values, application-wide configuration loaded at startup, and objects exported from modules that many consumers share — a frozen export cannot be quietly monkey-patched by one consumer in a way that breaks another. In strict mode, mutations of frozen objects throw immediately, converting silent data corruption into a loud stack trace at the exact offending line, which makes freeze a powerful development-time tripwire. Freezing also documents architectural boundaries in state-management code: reducers and selectors can hand out frozen snapshots to guarantee no component mutates shared state behind the store's back. Combine const (which locks the binding) with freeze (which locks the contents): const prevents reassignment of the variable, while freeze prevents modification of the object it points to — a genuine constant needs both. Skip freezing for short-lived internal objects where the ceremony adds nothing, and remember that deep protection of nested structures requires a recursive deepFreeze helper or a persistent-data library.
⚠️ Common Mistakes
Freezing class instances that maintain internal state - methods that write to this (counters, caches, lazy initialization) start failing after freeze, sometimes only in strict-mode contexts, producing bugs that appear and disappear depending on how the method is called. Freeze pure data objects, not stateful instances.
Expecting freeze to protect against reassignment of the variable itself - Object.freeze(config) does not stop config = otherObject. The binding needs const and the contents need freeze; each guards a different thing and neither implies the other.
Forgetting that frozen arrays reject push() and pop() - Object.freeze(list) makes mutation methods throw a TypeError in strict mode with messages like "Cannot add property 3, object is not extensible", which confuses developers who forget arrays are objects too. Derive new arrays with map(), filter(), or concat() instead.
Expecting Object.freeze() to be deep - it only freezes the top level. Nested objects remain mutable unless you freeze them recursively.
Not knowing that mutations fail silently in non-strict mode - always use "use strict" to get errors when modifying frozen objects.
Using Object.freeze() for security - determined attackers can still work around it. It is for integrity, not security.
✅ Best Practices
Freeze module-level exports of shared constants: export default Object.freeze({ ROLES, LIMITS }) guarantees no importer can mutate values that every other importer relies on - a one-line defense against action-at-a-distance bugs in large codebases.
Pair freezing with strict mode (ES modules are strict by default) so violations throw with a stack trace instead of failing silently - in sloppy mode a mutation bug can survive undetected until its stale data surfaces somewhere far from the cause.
Create a deepFreeze utility for truly immutable nested objects: function deepFreeze(obj) { Object.freeze(obj); Object.values(obj).filter(v => typeof v === "object").forEach(deepFreeze); return obj; }
Freeze configuration objects at application startup to catch accidental mutations early during development.
Consider using libraries like Immer for more practical immutability in complex applications.
⚡ Performance Notes
Freezing itself is a one-time O(n) pass that marks every own property non-writable and non-configurable and makes the object non-extensible. Reading frozen objects costs nothing extra in principle, but real engines differ: V8 has historically taken slower paths for some frozen-object operations (notably frozen arrays in certain versions), while frozen objects can also enable optimizations because the engine knows their shape can never change again. The practical guidance: freeze at startup or module load, not in hot loops, and avoid freezing objects created thousands of times per second — the marking pass would be paid repeatedly. A recursive deepFreeze over a large config tree does O(total properties) work once, which is fine at boot time. For enforcing immutability across big, frequently-updated state trees, structural-sharing libraries such as Immer scale better than freezing every snapshot.
🌍 Real World Example
Application Constants and Configuration
This example hardens application settings with a small recursive deepFreeze helper, closing the shallow-freeze loophole: freezing only the root would still let code overwrite APP_CONFIG.api.baseUrl. After deep freezing, an attempted change to the API endpoint is silently ignored (or throws in strict mode), and Object.isFrozen() confirms that both the root and the nested sections are locked. The pattern is standard for configuration loaded once at startup and trusted by every module afterward — an integrity measure, though not an access-control one.
// Deep freeze utility for nested objects
function deepFreeze(obj) {
Object.freeze(obj);
Object.values(obj)
.filter(value => value && typeof value === 'object')
.forEach(deepFreeze);
return obj;
}
// Application configuration - frozen to prevent accidental changes
const APP_CONFIG = deepFreeze({
api: {
baseUrl: 'https://api.example.com',
version: 'v1',
timeout: 30000
},
features: {
darkMode: true,
notifications: true,
analytics: false
},
limits: {
maxFileSize: 10 * 1024 * 1024, // 10MB
maxRetries: 3
}
});
// This will silently fail (or throw in strict mode)
APP_CONFIG.api.baseUrl = 'https://hacked.com';
console.log(APP_CONFIG.api.baseUrl);
// Still 'https://api.example.com'
// Check if frozen
console.log(Object.isFrozen(APP_CONFIG)); // true
console.log(Object.isFrozen(APP_CONFIG.api)); // true (because of deepFreeze)