Episode 1 — Fundamentals / 1.24 — Object Methods

1.24.g — Object.seal()

In one sentence: Object.seal(obj) prevents adding or deleting properties on an object but still allows modifying existing property values — a middle ground between fully open objects and Object.freeze().

Navigation: <- 1.24.f — Object.keys . 1.24.h — Object.values ->


1. What does Object.seal() do?

Object.seal() locks the shape of an object:

  • Cannot add new properties
  • Cannot delete existing properties
  • Cannot reconfigure property descriptors (e.g., change enumerable or configurable)
  • CAN modify existing property values (if they were writable)
const user = { name: "Alice", age: 30 };
Object.seal(user);

user.age = 31;             // ALLOWED -- modifying existing value
user.role = "admin";        // silently fails (or throws in strict mode)
delete user.name;           // silently fails (or throws in strict mode)

console.log(user);
// { name: "Alice", age: 31 }

2. Syntax and return value

Object.seal(obj)
ParameterDescription
objThe object to seal
ReturnsThe same object (not a copy)
const obj = { a: 1 };
const sealed = Object.seal(obj);
console.log(sealed === obj);  // true

3. What exactly happens when you seal?

When you call Object.seal(obj):

  1. The object becomes non-extensible (Object.isExtensible(obj) returns false)
  2. Every existing property becomes non-configurable (configurable: false)
  3. Properties that were writable remain writable
  4. Properties that were non-writable remain non-writable
const obj = { x: 10 };
Object.seal(obj);

const desc = Object.getOwnPropertyDescriptor(obj, "x");
console.log(desc);
// {
//   value: 10,
//   writable: true,       <-- still writable!
//   enumerable: true,
//   configurable: false    <-- changed to false
// }

4. Strict mode behavior

In strict mode (or ES modules), violations throw TypeError:

"use strict";

const config = Object.seal({ host: "localhost", port: 3000 });

config.port = 8080;   // OK -- modifying existing property

config.ssl = true;
// TypeError: Cannot add property ssl, object is not extensible

delete config.host;
// TypeError: Cannot delete property 'host' of #<Object>

In non-strict mode, violations silently fail.


5. Checking if an object is sealed

Object.isSealed(obj)
const obj = { a: 1 };
console.log(Object.isSealed(obj));  // false

Object.seal(obj);
console.log(Object.isSealed(obj));  // true

Special cases:

// An empty frozen object is also sealed
const frozen = Object.freeze({});
console.log(Object.isSealed(frozen));  // true  (freeze implies seal)

// Primitives are always sealed
console.log(Object.isSealed(42));       // true
console.log(Object.isSealed("hello"));  // true

6. seal vs freeze — detailed comparison

FeatureObject.seal()Object.freeze()
Add new propertiesNoNo
Delete existing propertiesNoNo
Modify existing valuesYesNo
Reconfigure descriptorsNoNo
Properties becomeconfigurable: falseconfigurable: false, writable: false
Object extensible?NoNo
Check methodObject.isSealed()Object.isFrozen()
Use caseFixed shape, mutable valuesComplete immutability
// Seal: shape is locked, values can change
const sealed = Object.seal({ count: 0, label: "clicks" });
sealed.count = 5;          // OK
sealed.newProp = true;     // blocked
delete sealed.label;       // blocked

// Freeze: nothing can change
const frozen = Object.freeze({ count: 0, label: "clicks" });
frozen.count = 5;          // blocked
frozen.newProp = true;     // blocked
delete frozen.label;       // blocked

Relationship between freeze, seal, and preventExtensions

Object.freeze(obj)  implies  Object.seal(obj)  implies  Object.preventExtensions(obj)

freeze  ⊂  seal  ⊂  preventExtensions

Every frozen object is sealed.
Every sealed object is non-extensible.
But NOT every sealed object is frozen (values may still be writable).
const frozen = Object.freeze({ a: 1 });
console.log(Object.isFrozen(frozen));       // true
console.log(Object.isSealed(frozen));       // true  (freeze implies seal)
console.log(Object.isExtensible(frozen));   // false

const sealed = Object.seal({ a: 1 });
console.log(Object.isFrozen(sealed));       // false (values still writable)
console.log(Object.isSealed(sealed));       // true
console.log(Object.isExtensible(sealed));   // false

7. Shallow seal

Like Object.freeze(), Object.seal() is shallow — nested objects are not sealed.

const app = Object.seal({
  name: "MyApp",
  settings: {
    theme: "dark",
    lang: "en",
  },
});

// Top-level sealed
app.version = "1.0";          // blocked
delete app.name;               // blocked

// Nested object NOT sealed
app.settings.theme = "light";  // ALLOWED
app.settings.newProp = true;   // ALLOWED
delete app.settings.lang;      // ALLOWED

console.log(app.settings);
// { theme: "light", newProp: true }

Deep seal pattern

function deepSeal(obj) {
  Object.seal(obj);
  for (const value of Object.values(obj)) {
    if (value !== null && typeof value === "object" && !Object.isSealed(value)) {
      deepSeal(value);
    }
  }
  return obj;
}

const config = deepSeal({
  app: "MyApp",
  db: { host: "localhost", port: 5432 },
});

config.db.host = "remote";    // OK (value change allowed)
config.db.newProp = true;     // blocked (sealed)
delete config.db.port;         // blocked (sealed)

8. Use cases

Fixed-shape form data

When you want a form data object to always have the same fields but allow value updates:

function createFormData() {
  return Object.seal({
    username: "",
    email: "",
    password: "",
    confirmPassword: "",
  });
}

const form = createFormData();

form.username = "alice";       // OK
form.email = "a@b.com";       // OK
form.hackerField = "evil";     // blocked -- typo or injection caught!

This catches typos too:

"use strict";
const form = Object.seal({ userName: "", email: "" });

form.username = "alice";
// TypeError! "username" !== "userName" -- the typo is caught!

Sealed API response objects

function createApiResponse(data) {
  return Object.seal({
    status: 200,
    data: data,
    error: null,
    timestamp: Date.now(),
  });
}

const response = createApiResponse({ users: [] });
response.status = 404;        // OK
response.data = null;          // OK
response.extraField = true;    // blocked

Game entity with fixed attributes

function createPlayer(name) {
  return Object.seal({
    name,
    health: 100,
    score: 0,
    level: 1,
    position: { x: 0, y: 0 },  // nested object NOT sealed (use deepSeal if needed)
  });
}

const player = createPlayer("Hero");
player.health = 80;           // OK
player.score = 500;           // OK
player.superPower = "fly";    // blocked -- no new properties

Configuration with updatable values

const dbConfig = Object.seal({
  host: "localhost",
  port: 5432,
  maxConnections: 10,
  timeout: 5000,
});

// Can update values (e.g., from environment)
dbConfig.host = process.env.DB_HOST || dbConfig.host;
dbConfig.port = Number(process.env.DB_PORT) || dbConfig.port;

// Cannot accidentally add typo properties
// dbConfig.timeOut = 3000;  // TypeError in strict mode!

9. Sealing arrays

const fixedArray = Object.seal([1, 2, 3]);

fixedArray[0] = 10;         // OK -- modifying existing index
fixedArray[1] = 20;         // OK

fixedArray.push(4);          // TypeError -- cannot add
fixedArray.pop();            // TypeError -- cannot delete
fixedArray[3] = 4;           // silently fails (or throws) -- index 3 does not exist

console.log(fixedArray);     // [10, 20, 3]

Note: splice, push, pop, shift, unshift all fail on sealed arrays because they add or remove elements.


10. Edge cases

// Sealing null/undefined throws TypeError
// Object.seal(null);       // TypeError
// Object.seal(undefined);  // TypeError

// Primitives return unchanged
Object.seal(42);     // 42
Object.seal("str");  // "str"

// Already sealed object -- no-op
const obj = Object.seal({ a: 1 });
Object.seal(obj);    // fine, returns obj

// Making a property non-writable on a sealed object
const sealed = Object.seal({ x: 10 });
// Cannot reconfigure, but you can use defineProperty to make writable -> non-writable
// (configurable is false, but writable: true -> false is still allowed per spec)
Object.defineProperty(sealed, "x", { writable: false });
sealed.x = 99;  // now blocked (writable is false)

That last edge case is subtle: on a sealed (non-configurable) property, you can reduce writable from true to false but cannot go from false back to true.


11. When to use seal vs freeze

ScenarioUse
Constants that must never changeObject.freeze()
Enum-like lookup objectsObject.freeze()
Fixed-shape objects that need updating (forms, state)Object.seal()
Catching typos in property namesObject.seal()
Preventing prototype pollutionEither works
No restrictions neededNeither

Key takeaways

  1. Object.seal(obj) prevents adding and deleting properties but allows value modification.
  2. Existing properties become non-configurable but remain writable (if they were before).
  3. It is shallow — nested objects are not automatically sealed.
  4. freeze implies seal — every frozen object is sealed, but not vice versa.
  5. Great for fixed-shape objects like form data, configs, and game entities.
  6. Use strict mode so violations throw TypeError instead of silently failing.

Explain-It Challenge

Explain without notes:

  1. You Object.seal an object. Can you change the value of an existing property? Can you add a new one?
  2. What is the key difference between Object.seal and Object.freeze?
  3. If you seal an object with a nested array, can you .push() to that array? Why?

Navigation: <- 1.24.f — Object.keys . 1.24.h — Object.values ->