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 andObject.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
enumerableorconfigurable) - 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)
| Parameter | Description |
|---|---|
obj | The object to seal |
| Returns | The 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):
- The object becomes non-extensible (
Object.isExtensible(obj)returnsfalse) - Every existing property becomes non-configurable (
configurable: false) - Properties that were writable remain writable
- 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
| Feature | Object.seal() | Object.freeze() |
|---|---|---|
| Add new properties | No | No |
| Delete existing properties | No | No |
| Modify existing values | Yes | No |
| Reconfigure descriptors | No | No |
| Properties become | configurable: false | configurable: false, writable: false |
| Object extensible? | No | No |
| Check method | Object.isSealed() | Object.isFrozen() |
| Use case | Fixed shape, mutable values | Complete 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
| Scenario | Use |
|---|---|
| Constants that must never change | Object.freeze() |
| Enum-like lookup objects | Object.freeze() |
| Fixed-shape objects that need updating (forms, state) | Object.seal() |
| Catching typos in property names | Object.seal() |
| Preventing prototype pollution | Either works |
| No restrictions needed | Neither |
Key takeaways
Object.seal(obj)prevents adding and deleting properties but allows value modification.- Existing properties become non-configurable but remain writable (if they were before).
- It is shallow — nested objects are not automatically sealed.
freezeimpliesseal— every frozen object is sealed, but not vice versa.- Great for fixed-shape objects like form data, configs, and game entities.
- Use strict mode so violations throw TypeError instead of silently failing.
Explain-It Challenge
Explain without notes:
- You
Object.sealan object. Can you change the value of an existing property? Can you add a new one? - What is the key difference between
Object.sealandObject.freeze? - 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 ->