cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Freezing, sealing & immutability

Lock JavaScript objects with preventExtensions, seal, and freeze; inspect the lock state; write a cycle-safe deepFreeze helper; and separate const from true immutability.

By the end, you can
  • 01
    Choose the right lockCompare preventExtensions, seal, and freeze by trying real operations.
  • 02
    Inspect descriptorsUse isExtensible, isSealed, isFrozen, and descriptors to explain what changed.
  • 03
    Avoid shallow surprisesExplain shallow freeze, write deepFreeze, and distinguish const from frozen.

Locking an object

Objects are flexible by default. You can add a property, replace a value, delete a property, or redefine its descriptor flags. That is useful while an object is being built, but it can be risky when the object represents configuration, a public API, or a value other code should not casually reshape.

This lesson is about the built-in object locks. They do not create a new object. They change the object you already have, in bulk, using the property descriptor ideas from Property flags & descriptors: configurable decides whether a property can be deleted or reconfigured, and writable decides whether a data property’s value can be assigned.

Real-life analogyA building with stronger locks

Imagine an object as a building. preventExtensions closes the building to new rooms: existing rooms still exist, and some can still be removed. seal also locks the walls: no new rooms and no demolition, but you may still redecorate a room by changing a writable value. freeze turns the building into a museum exhibit: no new rooms, no demolition, and no redecorating.

In real life: Close the building to new rooms
In JavaScript: Object.preventExtensions(obj)
In real life: Lock the walls against demolition
In JavaScript: Object.seal(obj)
In real life: Turn the building into a museum exhibit
In JavaScript: Object.freeze(obj)
In real life: Freeze every building on campus
In JavaScript: A recursive deepFreeze(obj)

Where the analogy stops: Real buildings have doors, owners, and keys. JavaScript locks are descriptor rules on one object. They do not automatically freeze objects stored inside it.

Strict mode matters. In strict code, failed writes throw a TypeError. In sloppy classic scripts, many failed writes silently do nothing. You met strict mode earlier; this lesson proves both outcomes in tests and shows strict results in the playground.

preventExtensions: no new rooms

INTERACTIVE

Object.preventExtensions(obj) is the lightest lock. It changes one big fact about the object: no new own properties can be added. Existing properties keep the descriptor flags they already had. If room was writable, you can still assign a new value. If it was configurable, you can still delete it.

That makes preventExtensions useful when the shape of an object must stop growing, but the existing fields are still active. It is like closing construction permits while letting people keep using and remodeling the rooms already inside.

Lock levels playground
Code under testPop out in the code editor (opens in a new tab)JavaScript
const settings = { theme: "dark", retries: 2, nested: { count: 1 } };// no locksettings.retries = 3;settings.newFlag = true;delete settings.theme;settings.nested.count = 2;Object.defineProperty(settings, "retries", { value: 4 });
Resultnone
Strict outcome
not run yet
Sloppy outcome
added extra
Object checks
extensible: true · sealed: false · frozen: false
room descriptor
writable: true · configurable: true
Try it yourself
Level:Try:

Choose a lock level and an operation. The strict result is computed when you press Run; the sloppy result is the same operation proved in tests with classic-script semantics.

Strict modules throw TypeError for blocked writes. Sloppy scripts usually fail silently for writes and return false for blocked delete; redefining a non-writable frozen value with defineProperty still throws.

Try preventExtensions with each operation. Adding fails. Changing visits works. Deleting room works because ordinary object-literal properties start configurable. Redefining visits works in this example because it is still configurable and writable. The nested write works because the nested object is a separate object.

Strict vs sloppy

The same blocked add produces different signals. Strict code throws so bugs are loud. Sloppy scripts often leave the object unchanged and keep going. Modern modules and this Next.js code are strict, but you may still read old snippets or classic scripts.

seal: no additions, no demolition

STEP THROUGH

Object.seal(obj) includes non-extensibility, then sets every own property’s configurable flag to false. Once sealed, properties cannot be deleted and their descriptor shape cannot be changed. Data properties that are still writable can still receive new values.

The building analogy says: no new rooms, and no knocking down walls. But if a room is writable, you can still redecorate it. That is why a sealed settings object can allow settings.retries = 3 while blocking delete settings.retries.

Watch seal and freeze rewrite descriptors
Step 0 of 6Ready
Your turn: follow the blue line

Step through the descriptor changes. Seal locks configurability; freeze also locks writability.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
Object.seal(ticket);const sealed = Object.getOwnPropertyDescriptors(ticket); const badge = { name: "Ada", score: 10 };Object.freeze(badge);const frozen = Object.getOwnPropertyDescriptors(badge);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Step through the descriptors. seal changes configurable to false; it does not change writable for data properties. Accessor properties, from the Getters & setters lesson, have no writable flag, so freezing and sealing affect their configurability, not the behavior inside a setter.

freeze: a museum exhibit

SORT

Object.freeze(obj) is the strongest built-in lock for an ordinary object. It makes the object non-extensible, makes every own property non-configurable, and for data properties sets writable to false. In strict code, assigning to a frozen data property throws a TypeError.

Freeze is still about properties. If a property contains a reference to another object, the reference cannot be swapped, but the object it points at may still be changed. That shallow behavior is the next section’s big surprise.

Allowed after freeze or seal?
  • Change an existing writable value after preventExtensions
  • Delete an existing configurable property after preventExtensions
  • Change an existing writable value after seal
  • Change obj.nested.count after freezing only obj
  • Delete a sealed own property
  • Assign a new value to a frozen data property
  • Add a brand-new property after preventExtensions
  • Delete a frozen own property
Try it yourself
0 of 8 correct

Sort each operation by the lock level where it is still allowed, or mark it blocked. Read every explanation; several cards are intentionally tricky.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Arrays are objects too

A frozen array cannot grow. In strict code, list.push(...) throws because push must add an index and update length. Freezing an array is common for constant lookup lists, but do not treat it as a performance trick.

Checking lock levels

JavaScript gives you three checks that match the locks:

  • Object.isExtensible(obj) asks whether new own properties may be added.
  • Object.isSealed(obj) asks whether the object is non-extensible and all own properties are non-configurable.
  • Object.isFrozen(obj) asks whether it is sealed and all own data properties are non-writable.
Lock-level matrix
StateExtensible?Sealed?Frozen?Existing writable values
Plain objecttruefalsefalseCan change
After preventExtensionsfalseUsually falseUsually falseCan change if writable
After sealfalsetrueMaybe falseCan change if writable
After freezefalsetruetrueCannot change data properties
Empty object after preventExtensionsfalsetruetrueNo properties to violate the rules
Fun edge case

An empty object that has only been passed to Object.preventExtensions is also sealed and frozen. There are no own properties left that could be configurable or writable, so the stricter checks pass.

When you need the exact flags, use Object.getOwnPropertyDescriptors(obj). The inspection methods are excellent summaries, but descriptors explain why a write, delete, or definition is allowed.

Deep freeze: every building on the campus

INTERACTIVE

Object.freeze is shallow. It freezes the main hall, not every building on campus. If team.lead points to another object, the lead property cannot point elsewhere after a freeze, but the lead object itself may still be mutable.

Freeze is shallow
Step 0 of 5Ready
Your turn: follow the blue line

Predict whether line 6 changes the nested value. Then step through the shallow freeze.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  name: "UI",  lead: { name: "Ada", points: 1 },};Object.freeze(team);team.lead.points = 2;console.log(team.lead.points);console.log(Object.isFrozen(team.lead));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

A deep freeze walks the object graph and freezes each nested object it reaches. A safe helper needs a WeakSet so cycles do not recurse forever: an object can point back to itself, or two properties can share the same nested object.

Deep freeze, arrays, and private fields
Cycle-safe deepFreezePop out in the code editor (opens in a new tab)JavaScript
function deepFreeze(value, seen = new WeakSet()) {  if (value === null || typeof value !== "object") return value;  if (seen.has(value)) return value;  seen.add(value);  for (const key of Reflect.ownKeys(value)) {    deepFreeze(value[key], seen);  }  return Object.freeze(value);}
Checksready
Nested write after deepFreeze
not run yet
Frozen checks
not run yet
Frozen array push
not run yet
Frozen instance with #private
not run yet
Try it yourself

Read the helper first. It returns primitives, skips objects it has already seen, recursively freezes property values, then freezes the object itself.

The helper is intentionally small. Production libraries often add choices for functions, typed arrays, Maps, Sets, or preserving prototypes.

Deep freezing is a defensive technique, not the whole story of immutability. Later, the Immutability lesson in Stage 8 focuses on patterns that create updated copies instead of mutating existing objects.

const vs frozen

INTERACTIVE

const and Object.freeze solve different problems. const locks a binding: the name cannot be pointed at a different value. It says nothing about what happens inside the object currently attached to that name.

Real-life analogyconst is a label on one building

A const binding is like a label glued to one building. It tells the label not to move to another building. It does not say whether people can repaint the rooms inside. Freezing is what locks the rooms.

In real life: A label glued to one building
In JavaScript: const profile = ... cannot be reassigned
In real life: People repaint rooms inside
In JavaScript: Properties can still change
In real life: Museum ropes inside the building
In JavaScript: Object.freeze(profile) blocks property changes

Where the analogy stops: A real label can fall off. A JavaScript const binding cannot be reassigned by code in the same scope, but object properties may still be mutable.

const and freeze compared
QuestionconstObject.freeze
What is locked?The binding/nameThe object’s own properties
Can obj.x = 2 work?Yes, if the property allows itNo for own frozen data properties in strict code
Can obj = {} work?No for a const bindingFreeze does not control the variable
Is it deep?Not about object contentsNo, shallow by default
const is a label, freeze is a lock
const vs Object.freezePop out in the code editor (opens in a new tab)JavaScript
const profile = { name: "Ada", score: 1 };profile.score = 2;      // allowed: the object changedObject.freeze(profile);profile.score = 3;      // blocked in strict code// profile = {};        // Syntax/runtime error: const label cannot point elsewhere
Outputready
Before freeze
not run yet
After freeze
not run yet
Object.isFrozen(profile)
not run yet
Try it yourself

Predict: does const stop line 2? Does freeze stop line 4? Run to separate the binding from the object.

The commented reassignment is not run because it would stop the example. It is shown as a reminder: const blocks rebinding the name.

Where you use it

These locks are most useful at boundaries. They make accidental mutation loud while code is being developed, and they document that a value should be treated as finished.

  • Freeze constant lookup data, such as a table of keyboard shortcuts or allowed roles.
  • Seal objects whose shape is part of an API, but whose values still update, such as a long-lived metrics record.
  • Prevent extensions when plugin code should not invent new fields on an object you own.
  • Deep freeze test fixtures so one test cannot accidentally mutate a fixture used by the next test.
Performance note

Do not freeze objects because someone promised a speed boost. Engine optimizations change, and frozen objects are not a reliable performance strategy. Use locks for correctness, clearer APIs, and catching mistakes.

Common misconceptions

“Freeze is deep.”

No. Object.freeze is shallow. Nested objects need to be frozen separately, usually with a recursive helper.

“const freezes objects.”

const locks the binding, not the object. A const object can still be mutated unless its properties prevent it.

“seal makes values read-only.”

Sealed writable data properties can still change. Seal blocks adding, deleting, and reconfiguring properties.

“Failed writes always throw.”

Strict code throws. Sloppy classic scripts often fail silently for assignment and return false for blocked delete.

“Frozen class instances cannot change any state.”

Private fields are not object properties. A method can still update #private state even when Object.isFrozen(instance) is true.

“Freeze is a speed button.”

Use it for correctness. Do not make broad performance claims without measuring in your real program.

Practice: lock objects on purpose

5 EXERCISES
Exercise 1 · Warm-upPredict a sealed object

Trace the code and type the two console lines in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const config = { mode: "dev", retries: 1 };
Object.seal(config);
config.retries = 2;
try { delete config.mode; } catch (error) { console.log(error.name); }
console.log(config.retries, config.mode);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict shallow freeze

    What does this code print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    "use strict";
    const config = { nested: { count: 1 } };
    Object.freeze(config);
    config.nested.count = 2;
    console.log(config.nested.count, Object.isFrozen(config.nested));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix a config object mutated by accident

      A shared config object is being changed by accident. Use deepFreeze when creating it, then verify the nested api object is frozen.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const config = deepFreeze({
        api: { retries: 2 },
      });
      try { config.api.retries = 9; } catch (error) { console.log(error.name); }
      console.log(Object.isFrozen(config.api));
        Exercise 4 · PracticeExplain const vs freeze

        Fill in the blank: const locks the ___, freeze locks the object.

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeUse Object.isFrozen checks

          You froze a config with deepFreeze. What expression should you use to prove the nested API options are frozen too?

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            These questions mix definitions with real output. Read the explanation for every choice, especially when you guess correctly.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does Object.preventExtensions(obj) block?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the sealed counter print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "use strict";
              const item = { count: 1 };
              Object.seal(item);
              item.count = 2;
              console.log(item.count);

              Choose an answer to see the explanation.

            3. Question 3 of 7Which descriptor flags does Object.freeze set on data properties?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does the empty-object edge case print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const empty = {};
              Object.preventExtensions(empty);
              console.log(Object.isSealed(empty), Object.isFrozen(empty));

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the shallow-freeze nested write print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const box = { nested: { value: 1 } };
              Object.freeze(box);
              box.nested.value = 2;
              console.log(box.nested.value);

              Choose an answer to see the explanation.

            6. Question 6 of 7After const user = { name: "Ada" }, which statement is true?

              Choose an answer to see the explanation.

            7. Question 7 of 7Why should a deepFreeze helper keep a WeakSet of seen objects?

              Choose an answer to see the explanation.

            Key takeaways

            • Object.preventExtensions blocks adding new own properties only.
            • Object.seal also makes own properties non-configurable, so deletion and reconfiguration are blocked.
            • Object.freeze also makes data properties non-writable.
            • Use Object.isExtensible, Object.isSealed, Object.isFrozen, and descriptors to inspect the exact state.
            • Freeze is shallow; a cycle-safe deepFreeze recursively freezes nested objects.
            • const locks the binding, not the object’s inside.

            Remember the one-liner.
            preventExtensions stops new properties; seal stops shape changes; freeze stops shape and writable data changes on that object.

            Up next: Enumerability & ownership, where you learn which properties loops and object methods can see.

            CompleteFrontend Clear concepts. Working examples.