Understanding Object.freeze() and Object.seal()
When building modern JavaScript applications, it’s surprisingly easy to mutate objects without noticing — and those unintended changes often lead to difficult, time‑consuming bugs. That’s why JavaScript provides two powerful tools for controlling mutability: Object.freeze() and Object.seal().
Both methods limit how objects can be changed, but they do so in very different ways. Let’s break them down with clear examples and real-world use cases.
What Object.freeze() Actually Does
Object.freeze() makes an object fully immutable. Once frozen:
- No new properties can be added
- Existing properties cannot be removed
- Values cannot be changed
- Property descriptors cannot be reconfigured
In short: the object becomes locked.
Example
const profile = { name: 'Alice', age: 30 };
Object.freeze(profile);
profile.age = 25; // ❌ ignored
profile.city = 'Paris'; // ❌ ignored
console.log(profile);
// { name: "Alice", age: 30 }Important: Freezing is shallow
Only the top-level object is frozen:
const user = {
name: 'Alice',
address: { city: 'London' }
};
Object.freeze(user);
user.address.city = 'Berlin'; // ✅ still allowedTo freeze everything deeply, you must recursively freeze nested objects.
What Object.seal() Does
Object.seal() is less strict. It locks the structure of the object, but keeps values editable.
A sealed object:
- ❌ cannot gain new properties
- ❌ cannot delete existing properties
- ❌ cannot reconfigure properties
- ✅ can modify existing values
Example
const car = { make: 'Chevrolet', model: 'Spark' };
Object.seal(car);
car.model = 'Camaro'; // ✅ allowed
car.year = 2020; // ❌ ignored
console.log(car);
// { make: "Chevrolet", model: "Camaro" }Sealing is perfect when you want predictable structure but flexible values.
When Should You Use Each?
Use Object.freeze() when:
- You want strict immutability
- You’re protecting config objects
- You’re preventing accidental mutations
- Working with Redux-like state patterns
Use Object.seal() when:
- You want fixed structure but editable values
- You’re building APIs or shared objects
- You want to prevent additions/removals but allow updates
Practical Use Cases
1. Protecting Config Objects
const config = Object.freeze({
apiUrl: 'https://api.example.com',
timeout: 5000
});
config.timeout = 2000; // ❌ ignoredThis ensures config remains stable throughout the app.
2. Redux / State Management
Immutability = predictable updates.
const initialState = Object.freeze({
count: 0,
user: null
});State updates return new frozen objects, preventing mutation bugs.
3. React: Safer Component State
const [settings, setSettings] = useState(
Object.freeze({ theme: 'light', notifications: true })
);
function toggleTheme() {
setSettings(prev =>
Object.freeze({
...prev,
theme: prev.theme === 'light' ? 'dark' : 'light'
})
);
}This protects state from accidental edits outside the setter.
4. Protecting Application Constants
const CONSTANTS = Object.freeze({
MAX_RETRIES: 5,
APP_NAME: 'MyApp'
});A reliable way to ensure constants stay constant.
Summary: freeze() vs seal()
| Feature | Object.freeze() | Object.seal() |
|---|---|---|
| Modify existing values | ❌ No | ✅ Yes |
| Add properties | ❌ No | ❌ No |
| Remove properties | ❌ No | ❌ No |
| Reconfigure descriptors | ❌ No | ❌ No |
| Freezes nested objects | ❌ No | ❌ No |
| Use case | Full immutability | Lock structure but allow value changes |
Both are excellent tools — you just need the right one for the job.
Final Thoughts
Object.freeze() and Object.seal() give you fine‑grained control over how objects can be changed. Whether you’re building safer React components, locking down API configurations, or avoiding mutation bugs in your state management system, these methods help enforce predictability and stability in your codebase.
If you want, I can also generate:
- a “Deep Freeze” utility snippet
- a version of this article optimized for Substack
- MDX with CodeHike syntax blocks
Just tell me!