Free lesson · JavaScript Basic to Advance
Property Access, Mutation, and Deletion
Working With Object Properties
Once an object exists, you need to read its properties, change them, add new ones, and sometimes remove them. JavaScript gives you two syntaxes for accessing properties and a few approaches for each kind of modification.
const user = {
name: "Olena",
age: 35,
role: "Head of Product"
};
// Reading
console.log(user.name);
console.log(user["age"]);
// Writing — modifying existing
user.age = 36;
// Writing — adding new
user.location = "Redmond";
// Deleting
delete user.role;
console.log(user);
These four operations — read, modify, add, delete — are the basic vocabulary for working with objects. This Topic covers each with the subtleties that catch people off guard.
Dot Notation vs Bracket Notation
There are two ways to access a property: dot notation (obj.key) and bracket notation (obj["key"]). They do the same thing, but each has cases where it's the only option.
Dot notation works when the key is a valid JavaScript identifier — letters, digits, underscores, dollar signs, doesn't start with a digit, isn't a reserved word.
const user = { name: "Olena", age: 35 };
console.log(user.name);
console.log(user.age);
// user.first-name // ❌ would be parsed as user.first - name
// user.2023 // ❌ syntax error, can't start with digit
Bracket notation works for any string key, including those that aren't valid identifiers:
const data = {
"first-name": "Olena",
"2023-revenue": 1000000,
"user id": 42
};
console.log(data["first-name"]);
console.log(data["2023-revenue"]);
console.log(data["user id"]);
Bracket notation also lets you use a variable as the key — something dot notation can't do:
const user = { name: "Olena", age: 35, role: "Head of Product" };
const keyName = "name";
console.log(user[keyName]); // "Olena"
console.log(user.keyName); // undefined — looks for property literally named "keyName"
This is the most common reason to use bracket notation: when you need to access a property whose name is determined at runtime. Looping over keys, accessing properties from configuration, looking up values by computed name — all of these use brackets.
graph LR
A[Property Access] --> B[Dot notation:<br/>obj.key]
A --> C[Bracket notation:<br/>obj key in brackets]
B --> D[Key is hardcoded identifier]
B --> E[Cannot use variable as key]
C --> F[Key can be any string]
C --> G[Can use variables/expressions]
style B fill:#7c3aed,color:#fff
style C fill:#10b981,color:#fff
This diagram contrasts dot notation and bracket notation for property access. Dot notation works only when the key is a hardcoded identifier that's a valid JavaScript name. You cannot use a variable as the key with dot notation. Bracket notation works for any string key, including names with special characters, and crucially allows you to use variables or computed expressions to determine the key at runtime.
Accessing Properties That Don't Exist
When you read a property that doesn't exist, JavaScript returns undefined instead of throwing an error:
const user = { name: "Olena" };
console.log(user.name); // "Olena"
console.log(user.age); // undefined
console.log(user.color); // undefined
This is convenient but can be a source of subtle bugs. Code that expects a value gets undefined and silently misbehaves. The fix is to check before using:
const user = { name: "Olena" };
if (user.location) {
console.log(`Lives in ${user.location}`);
} else {
console.log("Location not specified");
}
Modern JavaScript has optional chaining (?.) and nullish coalescing (??) for cleaner versions of these checks — we cover both in Module 10.
What's more dangerous is reading properties on undefined or null directly:
const user = { name: "Olena" };
console.log(user.address); // undefined — fine
// console.log(user.address.city); // ❌ TypeError: Cannot read property 'city' of undefined
Trying to access .city on undefined throws. The first level returns undefined quietly; chaining through it throws loudly. We'll return to safe access patterns in Module 10.
Interactive animation: Missing is safe, missing-dot-more is fatal - reading through undefined throws a TypeError.
Adding and Modifying Properties
Properties can be added to or modified on an object at any time:
const user = { name: "Olena" };
// Add new property
user.age = 35;
user["role"] = "Head of Product";
// Modify existing
user.name = "Olena Marchenko";
console.log(user);
There's no separate "add" vs "modify" syntax — assignment handles both. If the property doesn't exist, JavaScript creates it. If it exists, JavaScript overwrites it.
This works even on objects declared with const:
const user = { name: "Olena" };
user.age = 35; // ✅ allowed — modifying the object
user.name = "Nikhil"; // ✅ allowed — modifying a property
console.log(user);
// user = { name: "Someone Else" }; // ❌ would throw — reassigning the variable
We covered this in Topic 3.1: const prevents reassignment of the variable, not modification of the value it holds. To actually prevent modifications, you'd use Object.freeze — covered in Topic 7.7.
Interactive animation: The binding is frozen, the object is not - const allows changes inside but blocks reassignment.
Deleting Properties
The delete operator removes a property from an object entirely:
const user = { name: "Olena", age: 35, role: "Head of Product" };
delete user.role;
console.log(user);
console.log(user.role); // undefined — property no longer exists
After delete, the property is gone. It's not set to undefined — it doesn't exist at all. You can verify this with the in operator:
const user = { name: "Olena", age: 35 };
console.log("name" in user); // true
console.log("age" in user); // true
console.log("color" in user); // false
delete user.age;
console.log("age" in user); // false — property no longer there
delete is one of those operators you rarely use in modern code. More commonly, you create a new object without the property using spread syntax:
const user = { name: "Olena", age: 35, role: "Head of Product" };
// Instead of mutating with delete, create a new object
const { role, ...rest } = user;
const userWithoutRole = rest;
console.log(user); // original is unchanged
console.log(userWithoutRole); // new object without role
This pattern uses destructuring and the rest operator (covered in Module 5 and again in Module 10). It avoids mutation, which is preferred in functional-style code. Use delete when mutation is acceptable; use the non-mutating pattern when it isn't.
The Property Existence Check
There are three ways to check whether an object has a property, each with subtleties:
const user = {
name: "Olena",
age: 0,
role: undefined
};
// 1. Property access — returns undefined if missing
console.log(user.name); // "Olena"
console.log(user.role); // undefined
console.log(user.missing); // undefined
// Problem: can't distinguish "exists but is undefined" from "doesn't exist"
// 2. The 'in' operator — checks key existence
console.log("name" in user); // true
console.log("role" in user); // true — exists, even though value is undefined
console.log("missing" in user); // false
// Best for: distinguishing "set to undefined" vs "not set"
// 3. hasOwnProperty (older) / Object.hasOwn (modern)
console.log(Object.hasOwn(user, "name")); // true
console.log(Object.hasOwn(user, "missing")); // false
// Best for: checking own properties (ignoring inherited ones)
// We'll see why this matters when we cover prototypes in Topic 7.5
For most code, the difference doesn't matter and in is fine. The distinction becomes important when working with prototype inheritance, which we cover later in this module.
Interactive animation: Setting undefined keeps the key, delete removes it - and the in operator tells the two apart.
Common Pitfalls
Interactive animation: ?. stops instead of crashing - optional chaining returns undefined where a plain chain would throw.
Warning: The property access traps.
Chaining through possibly-missing properties.
user.address.citythrows ifaddressisundefined. Always check intermediate values, or use optional chaining (user?.address?.city).Confusing "missing" with "undefined". A property set to
undefinedexists. A missing property also returnsundefinedwhen accessed. To distinguish them, useinorObject.hasOwn.Using
deletein performance-sensitive code.deletecan prevent certain engine optimizations. In hot paths processing many objects, prefer creating new objects without the property instead.
Why This Matters in Interviews
Interview: Property access patterns appear constantly in coding problems. Interviewers watch whether you reach for dot notation when keys are static and bracket notation when they're dynamic. The wrong choice — using brackets with hardcoded strings, or dot notation with variables — signals unfamiliarity with the language.
The more conceptual question that comes up: "What's the difference between accessing a property that's set to undefined and one that doesn't exist?" The answer involves the
inoperator andObject.hasOwn.Topic 7.8 — Interview Lens: Objects and Prototypes — covers the deeper interview treatment.
Try It
Exercise: Build a small "settings" object and exercise all four operations on it:
- Create an object with at least three properties
- Read each property using a mix of dot and bracket notation
- Read a property that doesn't exist — observe
undefined- Modify one of the properties
- Add two new properties — one using a literal key, one using a variable as the key
- Use
into check whether a specific property exists- Delete one of the properties
- Verify with
inthat the deleted property is goneconst settings = { // your object here }; // your operations hereThe point of this exercise is muscle memory. By the time you finish, the syntax for each operation should be automatic.