Senior 5 min · March 05, 2026

JS Objects - Silent Mutation Broke User Sessions

Object mutation from reference copy in JavaScript corrupted user sessions: changing one user's email altered others.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • JavaScript objects store key-value pairs, bundling related data into one unit
  • Two access methods: dot notation (static keys) and bracket notation (dynamic keys)
  • Objects are reference types: assignment copies the reference, not the value
  • Accessing a missing property returns undefined silently — no error thrown
  • Performance tip: object property lookup is O(1) on average, but deleting properties can deoptimize hidden classes
  • Production insight: a typo in property name causes undefined and silent failures; use optional chaining or strict checks to catch early
Plain-English First

Think of a JavaScript object like a contact card in your phone. That card has a name, a phone number, an email, and maybe a photo — all bundled together under one person. In JavaScript, an object lets you group related pieces of information about one 'thing' into a single place, instead of scattering them across a dozen separate variables. Just like you'd look up someone by their name to find their number, you look up a property by its key to find its value.

Every real-world app you use — Instagram, Spotify, your bank's website — is built on data. A song has a title, an artist, a duration, and a genre. A user has a name, an email, and an age. If you tried to store all of that in separate, disconnected variables, your code would become impossible to manage before you even got started. JavaScript objects are the solution to this exact problem, and they're at the heart of almost everything you'll ever build on the web.

Before objects, imagine needing to track five pieces of information about 100 users. That's 500 variables. And when you pass data to a function, you'd need to pass all five separately every single time. Objects let you wrap all related data into one tidy package and hand it around your code as a single unit. They reflect how we naturally think about things in the real world — as entities with multiple characteristics.

By the end of this article you'll know how to create objects from scratch, read and update their properties using both dot notation and bracket notation, add and delete properties on the fly, loop over an object's contents, and understand the difference between primitive values and objects (a concept that trips up almost every beginner). You'll be ready to use objects confidently in real projects.

What Is a JavaScript Object and How Do You Create One?

An object in JavaScript is a collection of key-value pairs. The 'key' is the name of a piece of information (like 'firstName'), and the 'value' is the actual data stored under that name (like 'Sarah'). Together, each key-value pair is called a property.

The most common and readable way to create an object is with curly braces {}. This is called an object literal, and it's the go-to syntax for most JavaScript developers.

Inside the curly braces, you list your properties separated by commas. Each property is written as key: value. The key is usually written without quotes (unless it contains spaces or special characters), and the value can be any data type — a string, a number, a boolean, an array, or even another object.

Notice how grouping related data this way instantly makes your code more readable. Instead of six separate variables floating around, everything about a user lives in one place. That's the core win objects give you.

creatingObjects.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Creating an object using object literal syntax — the most common approach
const userProfile = {
  firstName: 'Sarah',          // string property
  lastName: 'Okonkwo',         // string property
  age: 28,                     // number property
  isPremiumMember: true,       // boolean property
  favouriteGenres: ['jazz', 'lo-fi', 'classical'], // array as a property value
};

// Let's see the whole object printed to the console
console.log(userProfile);

// You can also create an empty object first, then add properties later
const emptyCart = {};
console.log(emptyCart); // prints an empty object — useful when you build data dynamically
Output
{
firstName: 'Sarah',
lastName: 'Okonkwo',
age: 28,
isPremiumMember: true,
favouriteGenres: [ 'jazz', 'lo-fi', 'classical' ]
}
{}
Pro Tip:
Always use const when declaring objects unless you need to reassign the entire variable to a new object. const doesn't freeze the object — you can still change its properties freely. It just prevents you from accidentally pointing the variable at a completely different object later.
Production Insight
Object literals are the most performant way to create objects.
Avoid using new Object() – it's slower and adds no benefit.
Rule: always use literal syntax for static objects.
Key Takeaway
An object bundles related data under one variable.
Properties are written as key: value pairs.
Use const to declare objects to prevent reassignment.

Reading and Updating Object Properties — Dot vs Bracket Notation

Once you have an object, you need to get data out of it and put new data in. There are two ways to access a property: dot notation and bracket notation. Both do the same job — pick the right one for the situation.

Dot notation (object.propertyName) is cleaner and the one you'll use 90% of the time. Bracket notation (object['propertyName']) is more flexible — it lets you use a variable as the key, which is powerful when you don't know the property name until the code actually runs.

Updating a property is just as simple — you access it the same way and assign a new value with =. If the property doesn't exist yet, this same syntax creates it. There's no separate 'add property' command. Assignment does it all.

Deleting a property uses the delete keyword. It completely removes the key-value pair from the object, not just sets it to null. Use this with care — deleting properties can sometimes affect performance in JavaScript engines that optimise objects.

accessingProperties.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const bookListing = {
  title: 'The Pragmatic Programmer',
  author: 'David Thomas',
  pageCount: 352,
  isAvailable: true,
};

// --- DOT NOTATION --- clean and readable when you know the key name upfront
console.log(bookListing.title);       // reading the title property
console.log(bookListing.pageCount);   // reading the page count

// --- BRACKET NOTATION --- use this when the key is stored in a variable
const propertyToLookUp = 'author';    // we only know the key at runtime
console.log(bookListing[propertyToLookUp]); // bracket notation reads the variable's value as the key

// --- UPDATING a property --- same syntax, just assign a new value
bookListing.isAvailable = false;      // the book has been checked out
console.log(bookListing.isAvailable); // now false

// --- ADDING a new property --- just assign to a key that doesn't exist yet
bookListing.rating = 4.8;             // this property didn't exist before
console.log(bookListing.rating);      // JavaScript creates it on the spot

// --- DELETING a property ---
delete bookListing.pageCount;         // remove the pageCount property entirely
console.log(bookListing.pageCount);   // undefined — the property is gone
Output
The Pragmatic Programmer
352
David Thomas
false
4.8
undefined
Watch Out:
If you try to access a property that doesn't exist on an object, JavaScript won't throw an error — it silently returns undefined. This is a common source of bugs. If you see undefined where you expected a real value, check your property name for typos first. Keys are case-sensitive, so bookListing.Title and bookListing.title are completely different.
Production Insight
Bracket notation is necessary when keys have spaces or hyphens.
Performance: dot notation is slightly faster because engines optimise known keys.
Rule: prefer dot notation for readability; use bracket only when dynamic.
Key Takeaway
Dot notation is clean for known keys.
Bracket notation evaluates the key expression.
Use bracket for computed or invalid identifier keys.

Looping Over an Object and Nesting Objects Inside Objects

Most real-world objects contain more properties than you'd want to type out one by one. JavaScript gives you a for...in loop specifically designed to iterate over every key in an object. On each iteration, the loop gives you the current key as a string, and you use bracket notation to get the value.

Objects can also be nested — meaning a property's value can itself be an object. This is how real data is structured. Think of a user profile that has an address: the address has a street, a city, and a postcode. It makes sense to group those three inside their own nested object rather than flattening everything into one level.

To access deeply nested properties, you just chain dot notation: user.address.city. Read it left to right — start at user, go into the address object, then grab the city property inside it.

Be careful with for...in — it can loop over inherited properties from the object's prototype chain in some situations. Using hasOwnProperty() is a defensive habit that guarantees you only touch properties you actually defined.

loopingAndNesting.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// A nested object — the address is an object living inside userAccount
const userAccount = {
  username: 'galaxy_dev',
  email: 'alex@example.com',
  followersCount: 1240,
  address: {                        // nested object starts here
    street: '42 Maple Avenue',
    city: 'Bristol',
    postcode: 'BS1 4AA',
  },
};

// Accessing a nested property — chain dot notation
console.log(userAccount.address.city);     // drill into address, then grab city
console.log(userAccount.address.postcode); // same idea, different property

console.log('--- Looping over top-level properties ---');

// for...in loops over every key in the object
for (const key in userAccount) {
  // hasOwnProperty guards against accidentally looping over inherited properties
  if (userAccount.hasOwnProperty(key)) {
    console.log(`${key}: ${userAccount[key]}`); // bracket notation uses the key variable
  }
}

// Object.keys() is a modern alternative — returns an array of own keys only
console.log('--- Using Object.keys() ---');
const keys = Object.keys(userAccount);
console.log(keys); // clean array of just the property names
Output
Bristol
BS1 4AA
--- Looping over top-level properties ---
username: galaxy_dev
email: alex@example.com
followersCount: 1240
address: [object Object]
--- Using Object.keys() ---
[ 'username', 'email', 'followersCount', 'address' ]
Good to Know:
When you log a nested object inside a template literal (like ${userAccount[key]}), JavaScript converts it to the string [object Object] — not the full contents. To see nested object contents properly, use console.log(key, userAccount[key]) without a template literal, or use JSON.stringify(userAccount[key]) to convert it to a readable JSON string.
Production Insight
Looping with for...in can iterate over inherited enumerable properties.
Use Object.keys() or hasOwnProperty to avoid prototype pollution.
Rule: for modern code, prefer Object.entries() with for...of.
Key Takeaway
for...in loops over all enumerable properties, including inherited.
Object.keys() returns only own property names.
Nested objects require chained dot notation.

Objects Are Reference Types — the Concept That Trips Everyone Up

Here's the thing that bites almost every beginner at some point: objects in JavaScript don't behave like numbers or strings when you copy or compare them.

With a number or string (primitive types), when you assign one variable to another, you get a true independent copy. Change one and the other is unaffected.

With objects, when you assign one variable to another, you don't copy the object — you copy the reference to it. Both variables now point at the exact same object in memory. So if you change a property through one variable, the other variable reflects that change too, because they're both looking at the same thing.

This also means two objects that look identical are NOT equal when you compare them with ===, because === checks whether they're the same reference (the same object in memory), not whether they have the same content.

To make a true shallow copy of an object, use the spread operator ({...originalObject}) or Object.assign(). For deeply nested objects, you need a deep clone, which JSON.parse(JSON.stringify(obj)) handles for simple cases.

referenceTypes.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// --- Demonstrating the reference behaviour ---

const originalOrder = {
  item: 'Mechanical Keyboard',
  quantity: 1,
  dispatched: false,
};

// This does NOT create a copy — both variables point at the same object
const orderReference = originalOrder;

orderReference.dispatched = true; // we change it through orderReference...
console.log(originalOrder.dispatched); // ...but originalOrder reflects the change too!

// --- Making a true shallow COPY using the spread operator ---
const orderCopy = { ...originalOrder }; // spread creates a new object with the same properties

orderCopy.quantity = 3;               // changing the copy
console.log(originalOrder.quantity);  // original is untouched — they're now separate
console.log(orderCopy.quantity);      // the copy has the updated value

// --- Equality check on objects ---
const cartA = { product: 'Notebook', price: 4.99 };
const cartB = { product: 'Notebook', price: 4.99 };

console.log(cartA === cartB); // false — same content, but different objects in memory
console.log(cartA === cartA); // true — same reference, pointing at the exact same object
Output
true
1
3
false
true
Interview Gold:
The reference-vs-value distinction is one of the most frequently asked JavaScript interview questions. Be ready to explain that primitives are copied by value (independent copies) while objects are copied by reference (shared pointer). Knowing about spread ({...obj}) for shallow copies and JSON.parse(JSON.stringify(obj)) for deep copies will impress any interviewer.
Production Insight
Direct object assignment causes unintended shared state.
This is the source of many production bugs, especially in state management.
Rule: always use spread when you need an independent copy.
Key Takeaway
Objects are reference types: assignment copies the reference.
Use spread { ...obj } for shallow copy.
Use structuredClone() for deep copy.

Object Spreading and Merging – Combining Objects Safely

When you need to combine multiple objects into one, or create a new object from an existing one with modifications, the spread operator (...) is your best friend. Introduced in ES6, spread allows you to copy properties from one object into another elegantly.

For merging two objects, you can do: const merged = { ...objA, ...objB }. If both have the same property, the later source overwrites the earlier one. This is a shallow merge — nested objects are still shared references.

Object.assign() is the older alternative and does the same shallow merge. The spread syntax is generally preferred for readability.

Be careful with deep merging. A simple spread will not clone nested objects; they remain references. For a deep merge, you need to recursively merge or use a utility like Lodash's _.merge() or structuredClone() combined with spread.

objectMerging.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Shallow merge using spread
const defaults = { theme: 'light', notifications: true };
const userPrefs = { theme: 'dark', fontSize: 14 };
const config = { ...defaults, ...userPrefs };
console.log(config);
// { theme: 'dark', notifications: true, fontSize: 14 }

// Object.assign alternative
const config2 = Object.assign({}, defaults, userPrefs);

// Shallow copy and modify a property
const original = { a: 1, b: { c: 2 } };
const copy = { ...original, b: { ...original.b } }; // manually deep clone nested

// Deep merge using JSON (simple case, no functions/dates)
const deepCopy = JSON.parse(JSON.stringify(original));
Output
{ theme: 'dark', notifications: true, fontSize: 14 }
{ theme: 'dark', notifications: true, fontSize: 14 }
{ a: 1, b: { c: 2 } }
Common Trap:
Spread only does a shallow copy. If your object contains nested objects, those nested objects remain shared references. Mutating a nested property in the 'copy' will also mutate the original. Use structuredClone() for deep cloning, or manually deep copy nested levels.
Production Insight
Shallow merges are fast but can cause unintended mutations in nested data.
When merging API responses with defaults, ensure you deep-merge nested configs.
Rule: if your object has more than one level, reach for a deep merge utility.
Key Takeaway
Spread operator copies own enumerable properties into a new object.
Later properties overwrite earlier ones with the same key.
Shallow merge does not protect against nested mutations.
● Production incidentPOST-MORTEMseverity: high

The Silent Mutation That Corrupted User Sessions

Symptom
User profile changes in one part of the application unexpectedly appeared in other unrelated sessions. For example, changing a user's email from the admin panel would also change the email for other users sharing the same object reference.
Assumption
The developer assumed const copy = original creates a new copy of the object, similar to how primitive values work.
Root cause
Objects in JavaScript are copied by reference, not by value. The admin code assigned the original user object to a new variable without spreading it, so both variables pointed to the same object in memory. Any mutation through either variable affected the same data.
Fix
Replace direct assignment with a shallow copy: const copy = { ...original }; for deeply nested objects use structuredClone() or JSON.parse(JSON.stringify()).
Key lesson
  • Always assume object assignment is a reference copy unless you explicitly copy the object.
  • Use spread operator or structuredClone() when you need an independent snapshot.
  • Treat object mutation as a shared state risk; prefer immutable patterns in production.
Production debug guideSymptom → Action3 entries
Symptom · 01
Property returns undefined even though you're sure it exists
Fix
Check typo with console.dir(obj); also check property existence: 'key' in obj or obj.hasOwnProperty('key'). Log the entire object to inspect its keys.
Symptom · 02
Object mutation unexpectedly spreads to other variables
Fix
Identify where assignment happened. Did you use const copy = obj? If yes, replace with shallow copy: const copy = { ...obj }. For nested objects, use structuredClone() or JSON.parse(JSON.stringify()).
Symptom · 03
Two objects with identical content are not considered equal (=== returns false)
Fix
Understand that === compares references, not values. For content comparison, use JSON.stringify(obj1) === JSON.stringify(obj2) for flat objects, or a deep equality library like Lodash _.isEqual().
★ Object Debug Cheat SheetQuick commands to diagnose common object issues in the browser console or Node.js
Object mutation spreads unexpectedly
Immediate action
Check if direct assignment was used instead of spread
Commands
console.log('Original:', originalObj, 'Copy:', copyObj);
console.log('Same reference?', originalObj === copyObj);
Fix now
Replace copy = obj with copy = { ...obj }
Property returns undefined but you expected a value+
Immediate action
Log the object keys and check for typos
Commands
console.log(Object.keys(obj));
console.log('key' in obj, obj.hasOwnProperty('key'));
Fix now
Correct the property name or use optional chaining obj?.maybeMissing
Need to deep clone a nested object quickly+
Immediate action
Use JSON roundtrip for simple JSON-serializable data
Commands
const deepClone = JSON.parse(JSON.stringify(original));
console.log('Original:', original, 'Clone:', deepClone, 'Same?', original === deepClone);
Fix now
For dates, functions, or circular refs use structuredClone()
Feature / AspectDot Notation (obj.key)Bracket Notation (obj['key'])
ReadabilityCleaner, easier to scanMore verbose but equally valid
Key must be a valid identifierYes — no spaces or special chars in keyNo — any string works as a key
Key known at write timeYes — you type the key literallyNot required — can use a variable
Dynamic key lookupNot possibleCore strength — pass a variable as the key
When to useAlmost always, for static known keysWhen key name is computed or stored in a variable

Key takeaways

1
An object is a collection of key-value pairs
it bundles related data about one 'thing' into a single, manageable unit instead of scattering it across many variables.
2
Use dot notation for clean, readable access when you know the key name upfront; switch to bracket notation when the key is stored in a variable or computed at runtime.
3
Objects are reference types
assigning an object to a new variable doesn't copy it, it copies a pointer. Use { ...originalObject } (spread) to get a real shallow copy.
4
Accessing a non-existent property returns undefined silently
not an error. A typo in a property name is one of the most common and sneaky bugs in JavaScript.

Common mistakes to avoid

3 patterns
×

Treating object assignment as a copy

Symptom
Both variables point to the same object in memory, so mutations through either variable affect both.
Fix
Use the spread operator const newObj = { ...existingObj } to create a proper shallow copy.
×

Accessing a property that doesn't exist and assuming the code will error

Symptom
JavaScript returns undefined silently for missing properties, causing downstream bugs that are hard to trace.
Fix
Use optional chaining (obj?.propertyName) for uncertain access, or check 'propertyName' in obj before accessing. Never assume a property exists unless you created it.
×

Using `===` to compare two objects with identical content

Symptom
{ name: 'Alex' } === { name: 'Alex' } evaluates to false because JavaScript compares references, not content.
Fix
To compare object contents, use JSON.stringify(objA) === JSON.stringify(objB) for simple flat objects, or a library like Lodash's _.isEqual() for nested ones.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the difference between dot notation and bracket notation in Java...
Q02SENIOR
Can you explain what it means that objects in JavaScript are 'reference ...
Q03SENIOR
If I do `const a = {}; const b = {}; console.log(a === b);` — what does ...
Q01 of 03JUNIOR

What is the difference between dot notation and bracket notation in JavaScript, and when would you specifically choose bracket notation over dot notation?

ANSWER
Dot notation (obj.key) is cleaner and faster for property access when you know the exact key name at development time. Bracket notation (obj['key']) is necessary when the key name contains spaces or special characters, is a number, or is stored in a variable (dynamic key lookup). Bracket notation evaluates an expression inside the brackets, so it can also be used for computed properties. In production, prefer dot notation for readability and minor performance gains.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is an object in JavaScript?
02
What is the difference between an object and an array in JavaScript?
03
Why does changing a copied object also change the original?
04
How do I check if an object has a specific property?
🔥

That's JS Basics. Mark it forged?

5 min read · try the examples if you haven't

Previous
Arrays in JavaScript
7 / 16 · JS Basics
Next
Loops in JavaScript