Articles

Object.freeze() vs Object.seal() in JavaScript

Object.freeze() locks an object completely; Object.seal() only stops adding or removing properties. How the two immutability methods differ.

Takina Takina · · 4 min read
A dark-themed code editor showing JavaScript

Object.freeze() and Object.seal() are two built-in JavaScript methods that restrict how an object can be changed after it’s created, but they stop at different points: freeze() locks an object completely — no new properties, no deleted properties, no changed values — while seal() only prevents adding or removing properties, leaving existing property values free to be reassigned.

What each one actually locks

Both methods work on the object’s own properties and don’t touch anything about its prototype chain. The difference is in how far each one restricts mutation:

Object.seal(obj)

  • Prevents adding new properties.
  • Prevents deleting existing properties.
  • Allows reassigning the values of existing writable properties.

Object.freeze(obj)

  • Prevents adding new properties.
  • Prevents deleting existing properties.
  • Prevents reassigning the values of existing properties too.

In other words, every sealed object has one thing frozen objects don’t: you can still write sealedObj.count = 5 and have it take effect. A frozen object rejects that write silently in non-strict mode, or throws a TypeError in strict mode and inside ES modules (which are strict by default).

const sealed = Object.seal({ count: 0 });
sealed.count = 5;      // works — count is now 5
sealed.extra = "no";   // silently ignored — sealed objects reject new props
delete sealed.count;   // silently ignored

const frozen = Object.freeze({ count: 0 });
frozen.count = 5;      // silently ignored (throws in strict mode)
frozen.extra = "no";   // silently ignored
delete frozen.count;   // silently ignored

Comparison

Mutable objectObject.seal()Object.freeze()
Add new propertyYesNoNo
Delete propertyYesNoNo
Reassign existing valueYesYesNo
Reconfigure property descriptorYesNoNo
Checked with—Object.isSealed()Object.isFrozen()

Every frozen object is also, by definition, sealed — Object.isSealed() returns true for anything that passed through Object.freeze(). The reverse isn’t true: a sealed object is not automatically frozen, since its values can still change.

The shallow-freeze trap

Both methods are shallow. They lock the object’s own top-level properties, but if one of those properties holds a reference to another object or array, that nested object is completely unaffected.

const config = Object.freeze({
  name: "app",
  limits: { maxUsers: 100 },
});

config.name = "renamed";        // blocked, freeze works here
config.limits.maxUsers = 99999; // succeeds — limits object isn't frozen

This trips people up constantly: Object.freeze() looks like it should make an object immutable end to end, but it only ever freezes one level deep. A “deep freeze” requires recursively walking the object and calling Object.freeze() on every nested object and array yourself, or reaching for a library that does it for you.

Where these fit versus const

const and Object.freeze() solve different problems, and mixing them up is a common source of confusion. See var vs let vs const for how const behaves: it prevents reassigning the variable binding — you can’t point a const variable at a different object — but it does nothing to stop you from mutating the object that variable points to. Object.freeze() is the other way around: the variable can still be reassigned if it isn’t const, but the object’s own contents can’t be changed.

const user = { name: "Ada" };
user.name = "Grace"; // works fine — const doesn't stop this
user = {};           // TypeError — const stops this

Combining const obj = Object.freeze({...}) gets you both guarantees at once: the binding can’t be repointed, and the object’s own properties can’t be mutated.

When to actually use them

Object.freeze() is most useful for values that represent fixed configuration or constants shared across a codebase — an enum-like object, a set of feature flags loaded once at startup, or any object passed into code you don’t fully trust and don’t want silently mutated. It’s also a cheap guard in state-management patterns that assume state is never mutated directly, since a frozen state object will loudly fail (in strict mode) if something tries to bypass the intended update path — a useful complement to TypeScript’s readonly modifiers, which catch the same mistake at compile time but do nothing at runtime.

Object.seal() sees far less real-world use. It’s occasionally useful for objects with a known, fixed shape — you want to guarantee no code accidentally adds or typos a new property — while still allowing legitimate updates to the values that are there. In practice, most codebases that reach for immutability guarantees reach for freeze() directly rather than stopping at seal().

Neither method is a performance optimization in typical application code, though V8 and other engines can take advantage of a frozen object’s shape stability in hot paths. Reach for them for correctness and safety guarantees, not speed.

The takeaway

Object.seal() locks an object’s shape — no new or deleted properties — while leaving existing values writable; Object.freeze() locks the shape and the values, and is the one you’ll reach for in practice far more often. Both are shallow, so nested objects and arrays need their own explicit freeze if you want immutability all the way down, and both pair naturally with const to lock the variable binding too.

Takina Takina · · 4 min read

Object.groupBy() in JavaScript, Explained

Object.groupBy() buckets array items by a key you compute, replacing the reduce() boilerplate developers have written for years. How it works.

#JavaScript #Web Development #Frontend
Takina Takina · · 4 min read

Svelte 5 Runes Explained

Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.

#JavaScript #Frameworks #Web Development