Episode 1 — Fundamentals / 1.23 — Objects

1.23.d — Adding and Deleting Properties

In one sentence: JavaScript objects are mutable by default — you can add, modify, and delete properties at any time, check if a property exists with in or Object.hasOwn, and create non-mutating updates with the spread operator or Object.assign.

Navigation: ← 1.23 Overview · 1.23.e — Nested Objects →


1. Adding properties

You can add a new property to an existing object at any time:

const user = { name: "Alice" };

// Dot notation
user.age = 25;

// Bracket notation
user["email"] = "alice@example.com";

// Computed key
const field = "role";
user[field] = "admin";

console.log(user);
// { name: "Alice", age: 25, email: "alice@example.com", role: "admin" }

Adding properties to const objects

const prevents reassignment of the variable, not mutation of the object:

const config = { theme: "light" };

// This works — mutating the object
config.fontSize = 16;
config.theme = "dark";
console.log(config); // { theme: "dark", fontSize: 16 }

// This fails — reassigning the variable
// config = { theme: "blue" }; // TypeError: Assignment to constant variable

2. Modifying existing properties

Overwrite a property by assigning a new value:

const product = {
  name: "Widget",
  price: 9.99,
  inStock: true,
};

product.price = 12.99;       // update price
product.inStock = false;     // update availability
product.name = "Super Widget"; // update name

console.log(product);
// { name: "Super Widget", price: 12.99, inStock: false }

Conditional updates

const user = { name: "Alice", age: 25, role: "viewer" };

// Update only if condition is met
function updateUser(user, updates) {
  for (const [key, value] of Object.entries(updates)) {
    if (value !== undefined) {
      user[key] = value;
    }
  }
}

updateUser(user, { role: "admin", age: undefined }); // age stays unchanged
console.log(user); // { name: "Alice", age: 25, role: "admin" }

3. delete operator — removing properties entirely

The delete operator removes a property from an object:

const user = {
  name: "Alice",
  age: 25,
  tempToken: "abc123",
};

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

// tempToken is completely gone
console.log("tempToken" in user); // false

Bracket notation with delete

const headers = {
  "Content-Type": "application/json",
  "Authorization": "Bearer token123",
  "X-Debug": "true",
};

delete headers["X-Debug"];
console.log(headers);
// { "Content-Type": "application/json", "Authorization": "Bearer token123" }

4. delete returns true / false

delete returns a boolean — but the result is not always intuitive:

const obj = { a: 1, b: 2 };

console.log(delete obj.a);         // true — property removed
console.log(delete obj.nonExistent); // true — nothing to delete, but no error
console.log(delete obj);            // false — cannot delete a variable

// delete on non-configurable properties returns false:
const arr = [1, 2, 3];
console.log(delete arr.length);    // false — length is non-configurable

What delete does NOT do

  • Does not free memory directly (garbage collector handles that).
  • Does not work on variables, function declarations, or non-configurable properties.
  • Does not throw errors in non-strict mode (returns false silently).

5. Setting to undefined vs delete — a critical distinction

const user = { name: "Alice", age: 25, role: "admin" };

// Setting to undefined — property STILL EXISTS
user.role = undefined;
console.log(user.role);          // undefined
console.log("role" in user);     // true  — property exists!
console.log(Object.keys(user));  // ["name", "age", "role"] — still listed!

// Using delete — property is REMOVED
delete user.age;
console.log(user.age);           // undefined (same output, different reason)
console.log("age" in user);      // false — property is gone!
console.log(Object.keys(user));  // ["name", "role"]

Comparison table

obj.prop = undefineddelete obj.prop
obj.prop resultundefinedundefined
"prop" in objtruefalse
Shows in Object.keysYesNo
Shows in for...inYesNo
Shows in JSON.stringifyNo (undefined values skipped)No
Property descriptor existsYesNo

When to use each

  • delete — when the property should truly not exist (cleaning up sensitive data, removing optional fields).
  • = undefined — when you want to clear the value but keep the key in the object's shape (useful for maintaining consistent structure).

6. Property existence checks

Three ways to check if a property exists:

'key' in obj — checks own AND prototype chain

const user = { name: "Alice", age: 25 };

console.log("name" in user);       // true
console.log("email" in user);      // false
console.log("toString" in user);   // true — inherited from Object.prototype!

obj.hasOwnProperty('key') — checks own only

const user = { name: "Alice", age: 25 };

console.log(user.hasOwnProperty("name"));      // true
console.log(user.hasOwnProperty("toString"));  // false — inherited, not own

Object.hasOwn(obj, 'key') — ES2022, preferred

const user = { name: "Alice", age: 25 };

console.log(Object.hasOwn(user, "name"));      // true
console.log(Object.hasOwn(user, "toString"));  // false
console.log(Object.hasOwn(user, "email"));     // false

Why Object.hasOwn is preferred over hasOwnProperty

// Edge case: object created with no prototype
const bare = Object.create(null);
bare.key = "value";

// bare.hasOwnProperty("key"); // TypeError! — no prototype, no method
console.log(Object.hasOwn(bare, "key")); // true — works on any object

Comparison table

MethodChecks own?Checks prototype?Safe on Object.create(null)?ES version
"key" in objYesYesYesES1
obj.hasOwnProperty("key")YesNoNoES3
Object.hasOwn(obj, "key")YesNoYesES2022

7. Spread operator for non-mutating updates

The spread operator ... creates a shallow copy with overrides — the original is untouched:

const original = { name: "Alice", age: 25, role: "viewer" };

// Create a new object with updated role
const updated = { ...original, role: "admin" };

console.log(updated);  // { name: "Alice", age: 25, role: "admin" }
console.log(original); // { name: "Alice", age: 25, role: "viewer" } — unchanged!

Order matters — later properties win

const defaults = { theme: "light", fontSize: 14, language: "en" };
const userPrefs = { theme: "dark", fontSize: 18 };

// User preferences override defaults
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: "dark", fontSize: 18, language: "en" }

// Reverse order — defaults would override user preferences (wrong!)
const wrong = { ...userPrefs, ...defaults };
console.log(wrong);
// { theme: "light", fontSize: 14, language: "en" } — user prefs lost!

Removing a property immutably with destructuring + rest

const user = { name: "Alice", age: 25, tempToken: "abc123" };

// Remove tempToken without mutating original
const { tempToken, ...cleanUser } = user;

console.log(cleanUser);  // { name: "Alice", age: 25 }
console.log(user);       // { name: "Alice", age: 25, tempToken: "abc123" } — unchanged
console.log(tempToken);  // "abc123" — captured in its own variable

8. Object.assign for merging (mutating)

Object.assign(target, ...sources) copies properties from sources into the target object:

const target = { a: 1, b: 2 };
const source1 = { b: 3, c: 4 };
const source2 = { d: 5 };

Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 3, c: 4, d: 5 } — target is MUTATED

Non-mutating pattern — use an empty target

const original = { name: "Alice", age: 25 };
const updates = { age: 26, email: "alice@example.com" };

// New object — original is untouched
const merged = Object.assign({}, original, updates);

console.log(merged);   // { name: "Alice", age: 26, email: "alice@example.com" }
console.log(original); // { name: "Alice", age: 25 } — unchanged

Object.assign vs spread

FeatureObject.assign(target, src){ ...src1, ...src2 }
Mutates target?Yes (unless target is {})No (always new object)
Return valueThe target objectA new object
Setters on targetTriggers settersDoes not (copies values)
ReadabilityLongerCleaner
RecommendationUse when you need mutation or setter behaviorPreferred for immutable patterns

9. Real examples

Updating a user profile

const user = {
  id: 42,
  name: "Alice",
  email: "alice@example.com",
  bio: "Developer",
  avatar: "alice.png",
};

// Mutable update
function updateProfile(user, updates) {
  for (const [key, value] of Object.entries(updates)) {
    if (Object.hasOwn(user, key)) {
      user[key] = value;
    }
  }
}

updateProfile(user, { bio: "Senior Developer", avatar: "alice-new.png" });
console.log(user.bio); // "Senior Developer"

// Immutable update
function updateProfileImmutable(user, updates) {
  return { ...user, ...updates };
}

const updatedUser = updateProfileImmutable(user, { name: "Alice J." });
console.log(updatedUser.name); // "Alice J."
console.log(user.name);        // "Alice" — still "Alice" if original wasn't mutated before

Removing optional fields before API submission

const formData = {
  username: "alice",
  email: "alice@example.com",
  phone: "",
  bio: "",
  avatar: null,
};

// Remove empty/null optional fields
function cleanFormData(data, requiredFields) {
  const cleaned = { ...data };
  for (const key of Object.keys(cleaned)) {
    if (!requiredFields.includes(key) && (cleaned[key] === "" || cleaned[key] === null)) {
      delete cleaned[key];
    }
  }
  return cleaned;
}

const payload = cleanFormData(formData, ["username", "email"]);
console.log(payload);
// { username: "alice", email: "alice@example.com" }
// phone, bio, avatar removed — but username and email kept even if empty

Key takeaways

  1. Add properties anytime: obj.newKey = value or obj["new-key"] = value.
  2. const prevents variable reassignment, not object mutation.
  3. delete obj.prop removes the property entirely; obj.prop = undefined keeps the key.
  4. Check existence with Object.hasOwn(obj, key) (ES2022, safest) or "key" in obj (includes prototype).
  5. Spread { ...obj, key: newValue } creates non-mutating updates — the original is preserved.
  6. Object.assign mutates the target; use Object.assign({}, obj) for a non-mutating copy.
  7. Destructuring + rest const { removed, ...rest } = obj is the cleanest way to remove a property immutably.

Explain-It Challenge

Explain without notes:

  1. What is the difference between delete obj.prop and obj.prop = undefined? Show with "prop" in obj.
  2. Why is Object.hasOwn preferred over obj.hasOwnProperty?
  3. How does the spread operator create a non-mutating update? What is the limitation with nested objects?

Navigation: ← 1.23 Overview · 1.23.e — Nested Objects →