Welcome back! 👋 In the previous lesson, you mastered map, filter, and reduce. Now let's go deep into one of the most fundamental data structures in all of JavaScript — Objects!
If arrays are for ordered lists, objects are for structured, named data. Every user profile, product listing, API response, configuration setting, and database record you'll ever work with in JavaScript is an object. Understanding objects deeply is what separates beginners from professional developers.
Let's master them completely!
What is an Object?
An object is a collection of related data stored as key-value pairs. Each key (also called a property) has a name and a corresponding value.
let person = {
name: "Mihir", // key: "name", value: "Mihir"
age: 25, // key: "age", value: 25
city: "Mumbai", // key: "city", value: "Mumbai"
active: true, // key: "active",value: true
};Think of an object like a real-world entity — a person has a name, age, city. A product has a name, price, category. An object groups all these related pieces together.
Creating Objects
Object Literal (Most Common)
let student = {
name: "Mihir",
marks: 92,
dept: "Computer Science",
active: true,
};Empty Object — Add Properties Later
let user = {};
user.name = "Priya";
user.email = "priya@example.com";
user.role = "Editor";
console.log(user);
// { name: "Priya", email: "priya@example.com", role: "Editor" }Using new Object()
let car = new Object();
car.brand = "Toyota";
car.model = "Camry";
// ✅ Prefer object literals — they are cleaner and more readableAccessing Properties
Dot Notation (Most Common)
let user = { name: "Mihir", age: 25, city: "Mumbai" };
console.log(user.name); // "Mihir"
console.log(user.age); // 25
console.log(user.city); // "Mumbai"Bracket Notation — Use When Key is Dynamic
let user = { name: "Mihir", age: 25, city: "Mumbai" };
// Access with a variable key
let key = "name";
console.log(user[key]); // "Mihir"
// Access keys with spaces or special characters
let config = { "font-size": 16, "background-color": "dark" };
console.log(config["font-size"]); // 16 ✅
// config.font-size — ❌ SyntaxErrorDot vs Bracket Notation
let product = { name: "Laptop", price: 55000 };
// ✅ Dot notation — clean for known, simple keys
console.log(product.name);
// ✅ Bracket notation — required for dynamic/variable keys
let field = "price";
console.log(product[field]); // 55000
// ✅ Bracket notation — required for keys with spaces/special chars
let obj = { "first name": "Mihir" };
console.log(obj["first name"]); // "Mihir"Accessing Non-Existent Properties
let user = { name: "Mihir" };
console.log(user.age); // undefined — no error
console.log(user.address.city); // ❌ TypeError — cannot read 'city' of undefined
// ✅ Use optional chaining to safely access nested properties
console.log(user.address?.city); // undefined — safe ✅Adding, Updating and Deleting Properties
Add a Property
let user = { name: "Mihir" };
user.age = 25; // add new property
user.city = "Mumbai"; // add another
user["role"] = "Admin"; // bracket notation also works
console.log(user);
// { name: "Mihir", age: 25, city: "Mumbai", role: "Admin" }Update a Property
let user = { name: "Mihir", age: 25, role: "Viewer" };
user.age = 26; // update age
user.role = "Admin"; // update role
console.log(user);
// { name: "Mihir", age: 26, role: "Admin" }Delete a Property
let user = { name: "Mihir", age: 25, temp: "delete me" };
delete user.temp;
console.log(user); // { name: "Mihir", age: 25 }Check if a Property Exists
let user = { name: "Mihir", age: 25 };
// ✅ Using 'in' operator
console.log("name" in user); // true
console.log("email" in user); // false
// ✅ Using hasOwnProperty
console.log(user.hasOwnProperty("name")); // true
console.log(user.hasOwnProperty("email")); // false
// ⚠️ Don't use undefined check — misses falsy values
console.log(user.age !== undefined); // true — but fragileObject Methods
An object can store functions as values — these are called methods.
Defining Methods
let calculator = {
brand: "MathPro",
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
},
multiply(a, b) {
return a * b;
},
};
console.log(calculator.add(10, 5)); // 15
console.log(calculator.subtract(10, 5)); // 5
console.log(calculator.multiply(10, 5)); // 50this Inside Methods
Inside a method, this refers to the object the method belongs to.
let user = {
name: "Mihir",
age: 25,
greet() {
console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);
},
isAdult() {
return this.age >= 18;
},
};
user.greet(); // Hi, I'm Mihir and I'm 25 years old.
console.log(user.isAdult()); // true⚠️ Arrow Functions Don't Work as Methods
let user = {
name: "Mihir",
// ❌ Arrow function — 'this' is NOT the object
greet: () => {
console.log(`Hi, ${this.name}`); // undefined!
},
// ✅ Regular function — 'this' correctly refers to user
greetCorrect() {
console.log(`Hi, ${this.name}`); // "Hi, Mihir" ✅
},
};
user.greet(); // Hi, undefined ❌
user.greetCorrect(); // Hi, Mihir ✅Computed Property Keys
Use [] to define a property key dynamically.
let field = "name";
let user = {
[field]: "Mihir", // key becomes "name"
age: 25,
};
console.log(user.name); // "Mihir"// Build object keys from variables
function createSetting(key, value) {
return { [key]: value };
}
console.log(createSetting("theme", "dark")); // { theme: "dark" }
console.log(createSetting("fontSize", 16)); // { fontSize: 16 }
console.log(createSetting("language", "en")); // { language: "en" }// Dynamic keys from array
let fields = ["name", "age", "city"];
let values = ["Mihir", 25, "Mumbai"];
let person = fields.reduce((obj, field, i) => {
obj[field] = values[i];
return obj;
}, {});
console.log(person); // { name: "Mihir", age: 25, city: "Mumbai" }Shorthand Property Names
When the variable name matches the key name, you can use shorthand.
let name = "Mihir";
let age = 25;
let city = "Mumbai";
// ❌ Old way — redundant
let user = { name: name, age: age, city: city };
// ✅ Shorthand — cleaner
let user = { name, age, city };
console.log(user); // { name: "Mihir", age: 25, city: "Mumbai" }Nested Objects
Objects can contain other objects — perfect for representing complex real-world data.
let student = {
name: "Mihir",
marks: {
math: 92,
science: 88,
english: 85,
},
address: {
city: "Mumbai",
state: "Maharashtra",
pin: "400001",
},
};
// Accessing nested properties
console.log(student.marks.math); // 92
console.log(student.address.city); // "Mumbai"
console.log(student["marks"]["science"]); // 88Deep Nesting
let company = {
name: "TechCorp",
ceo: {
name: "Rahul Mehta",
contact: {
email: "rahul@techcorp.com",
phone: "9876543210",
},
},
departments: {
engineering: { headCount: 50, budget: 5000000 },
design: { headCount: 15, budget: 1500000 },
},
};
console.log(company.ceo.name); // "Rahul Mehta"
console.log(company.ceo.contact.email); // "rahul@techcorp.com"
console.log(company.departments.engineering.headCount); // 50Built-in Object Methods
Object.keys() — Get All Keys
let user = { name: "Mihir", age: 25, city: "Mumbai" };
console.log(Object.keys(user)); // ["name", "age", "city"]Object.values() — Get All Values
let user = { name: "Mihir", age: 25, city: "Mumbai" };
console.log(Object.values(user)); // ["Mihir", 25, "Mumbai"]Object.entries() — Get Key-Value Pairs
let user = { name: "Mihir", age: 25, city: "Mumbai" };
console.log(Object.entries(user));
// [["name", "Mihir"], ["age", 25], ["city", "Mumbai"]]
// Loop over entries cleanly
for (let [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`);
}
// Output:
// name: Mihir
// age: 25
// city: MumbaiObject.assign() — Merge Objects
let defaults = { theme: "light", fontSize: 14, lang: "en" };
let userPrefs = { theme: "dark", fontSize: 16 };
// Merge — userPrefs overrides defaults
let config = Object.assign({}, defaults, userPrefs);
console.log(config);
// { theme: "dark", fontSize: 16, lang: "en" }Object.freeze() — Make Object Immutable
let config = Object.freeze({
apiUrl: "https://api.example.com",
version: "1.0",
debug: false,
});
config.debug = true; // Silently ignored ❌
config.newKey = "test"; // Silently ignored ❌
console.log(config.debug); // false — unchanged ✅Object.fromEntries() — Array of Pairs to Object
let entries = [["name", "Mihir"], ["age", 25], ["city", "Mumbai"]];
let user = Object.fromEntries(entries);
console.log(user); // { name: "Mihir", age: 25, city: "Mumbai" }
// Practical: Transform object values using Map
let prices = { laptop: 55000, mouse: 800, monitor: 18000 };
let withTax = Object.fromEntries(
Object.entries(prices).map(([key, val]) => [key, Math.round(val * 1.18)])
);
console.log(withTax);
// { laptop: 64900, mouse: 944, monitor: 21240 }Copying and Cloning Objects
Shallow Copy with Spread ...
let original = { name: "Mihir", age: 25, city: "Mumbai" };
let copy = { ...original }; // Shallow copy
copy.name = "Priya"; // Only changes the copy
console.log(original.name); // "Mihir" — unchanged ✅
console.log(copy.name); // "Priya"Shallow vs Deep Copy
let original = {
name: "Mihir",
address: { city: "Mumbai" }, // nested object
};
// Shallow copy — nested objects are still shared!
let shallow = { ...original };
shallow.address.city = "Pune"; // ❌ Also changes original!
console.log(original.address.city); // "Pune" — oops!
// Deep copy — fully independent copy
let deep = JSON.parse(JSON.stringify(original));
deep.address.city = "Delhi";
console.log(original.address.city); // "Pune" — unchanged ✅Merge Two Objects with Spread
let personal = { name: "Mihir", age: 25 };
let work = { role: "Developer", dept: "Engineering" };
let combined = { ...personal, ...work };
console.log(combined);
// { name: "Mihir", age: 25, role: "Developer", dept: "Engineering" }
// Later keys override earlier keys
let updated = { ...personal, name: "Priya", city: "Pune" };
console.log(updated);
// { name: "Priya", age: 25, city: "Pune" }Optional Chaining ?.
Safely access deeply nested properties without crashing when a value is null or undefined.
let user = {
name: "Mihir",
address: {
city: "Mumbai",
},
};
// ❌ Without optional chaining — crashes if address is missing
console.log(user.contact.phone); // TypeError!
// ✅ With optional chaining — returns undefined safely
console.log(user.contact?.phone); // undefined
console.log(user.address?.city); // "Mumbai"
console.log(user.address?.pin?.code); // undefined — chained safelyReal-World Examples
Example 1: User Profile Manager
function createUser(name, email, role = "viewer") {
return {
id: Date.now(),
name,
email,
role,
createdAt: new Date().toLocaleDateString(),
active: true,
promote(newRole) {
this.role = newRole;
console.log(`${this.name} promoted to ${this.role}`);
},
deactivate() {
this.active = false;
console.log(`${this.name}'s account deactivated`);
},
display() {
console.log(`[${this.active ? "✅" : "❌"}] ${this.name} | ${this.email} | ${this.role}`);
},
};
}
let user1 = createUser("Mihir", "mihir@example.com", "admin");
let user2 = createUser("Priya", "priya@example.com");
user1.display(); // ✅ Mihir | mihir@example.com | admin
user2.display(); // ✅ Priya | priya@example.com | viewer
user2.promote("editor");
user2.display(); // ✅ Priya | priya@example.com | editor
user2.deactivate();
user2.display(); // ❌ Priya | priya@example.com | editorExample 2: Product Catalog
let catalog = {
products: [
{ id: 1, name: "Laptop", price: 55000, category: "Electronics", inStock: true },
{ id: 2, name: "T-Shirt", price: 799, category: "Clothing", inStock: true },
{ id: 3, name: "Phone", price: 18000, category: "Electronics", inStock: false },
{ id: 4, name: "Jeans", price: 1499, category: "Clothing", inStock: true },
],
getByCategory(category) {
return this.products.filter(p => p.category === category);
},
getInStock() {
return this.products.filter(p => p.inStock);
},
getById(id) {
return this.products.find(p => p.id === id) ?? null;
},
getTotalValue() {
return this.products.reduce((sum, p) => sum + p.price, 0);
},
addProduct(product) {
this.products.push({ id: this.products.length + 1, ...product });
console.log(`✅ Added: ${product.name}`);
},
};
console.log("Electronics:", catalog.getByCategory("Electronics").map(p => p.name));
console.log("In Stock:", catalog.getInStock().map(p => p.name));
console.log("Product #2:", catalog.getById(2));
console.log("Total Value: ₹" + catalog.getTotalValue().toLocaleString());
catalog.addProduct({ name: "Earbuds", price: 2500, category: "Electronics", inStock: true });
console.log("After add:", catalog.products.length, "products");
// Output:
// Electronics: ["Laptop", "Phone"]
// In Stock: ["Laptop", "T-Shirt", "Jeans"]
// Product #2: { id: 2, name: "T-Shirt", ... }
// Total Value: ₹75,298
// ✅ Added: Earbuds
// After add: 5 productsExample 3: Configuration Manager
const defaultConfig = Object.freeze({
theme: "light",
language: "en",
fontSize: 14,
notifications: true,
autoSave: true,
currency: "INR",
});
function createConfig(userOverrides = {}) {
let config = { ...defaultConfig, ...userOverrides };
return {
get(key) {
return config[key];
},
set(key, value) {
if (!(key in defaultConfig)) {
console.log(`⚠️ Unknown setting: "${key}"`);
return;
}
config[key] = value;
console.log(`✅ Updated: ${key} → ${value}`);
},
reset() {
config = { ...defaultConfig };
console.log("🔄 Config reset to defaults");
},
display() {
console.log("\n⚙️ Current Settings:");
for (let [key, val] of Object.entries(config)) {
let display = typeof val === "boolean" ? (val ? "✅ On" : "❌ Off") : val;
console.log(` ${key.padEnd(16)}: ${display}`);
}
},
};
}
let config = createConfig({ theme: "dark", fontSize: 16 });
config.display();
config.set("language", "hi");
config.set("unknownKey", "value"); // ⚠️ warning
config.reset();
config.display();Example 4: Grade Calculator with Object
let gradebook = {
students: {},
add(name, ...scores) {
let avg = scores.reduce((s, n) => s + n, 0) / scores.length;
let grade = avg >= 90 ? "A" : avg >= 75 ? "B" : avg >= 60 ? "C" : "F";
this.students[name] = { scores, average: parseFloat(avg.toFixed(1)), grade };
},
getToppers() {
return Object.entries(this.students)
.filter(([, s]) => s.grade === "A")
.map(([name]) => name);
},
getClassAverage() {
let avgs = Object.values(this.students).map(s => s.average);
return (avgs.reduce((s, n) => s + n, 0) / avgs.length).toFixed(1);
},
display() {
console.log("\n=== 📊 Gradebook ===");
for (let [name, data] of Object.entries(this.students)) {
console.log(`${name.padEnd(10)} | Avg: ${data.average} | Grade: ${data.grade}`);
}
console.log(`\nToppers: ${this.getToppers().join(", ")}`);
console.log(`Class Avg: ${this.getClassAverage()}`);
},
};
gradebook.add("Mihir", 92, 88, 95);
gradebook.add("Priya", 78, 82, 80);
gradebook.add("Rahul", 55, 60, 58);
gradebook.add("Sara", 95, 92, 98);
gradebook.display();
// Output:
// Mihir | Avg: 91.7 | Grade: A
// Priya | Avg: 80.0 | Grade: B
// Rahul | Avg: 57.7 | Grade: F
// Sara | Avg: 95.0 | Grade: A
// Toppers: Mihir, Sara
// Class Avg: 81.1Common Mistakes
Mistake 1: Accessing Property of undefined
let user = { name: "Mihir" };
// ❌ address doesn't exist — accessing city crashes!
console.log(user.address.city); // TypeError ❌
// ✅ Use optional chaining
console.log(user.address?.city); // undefined — safe ✅Mistake 2: Confusing = (assign) and : (define)
// ❌ Using = inside object literal — SyntaxError!
let user = {
name = "Mihir", // SyntaxError!
};
// ✅ Use : inside object literal
let user = {
name: "Mihir", // ✅
};
// = is for assigning after the object is created
user.age = 25; // ✅Mistake 3: Shallow Copy Pitfall with Nested Objects
let original = { name: "Mihir", scores: [92, 85, 78] };
// ❌ Shallow copy — nested array is still shared!
let copy = { ...original };
copy.scores.push(100); // Modifies BOTH copy and original!
console.log(original.scores); // [92, 85, 78, 100] — oops! ❌
// ✅ Deep copy for nested structures
let deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.scores.push(999);
console.log(original.scores); // [92, 85, 78, 100] — safe ✅Mistake 4: Using Arrow Function as Object Method
let counter = {
count: 0,
// ❌ Arrow function — 'this' is not the counter object
increment: () => {
this.count++; // 'this' is undefined or global!
console.log(this.count); // NaN or error
},
// ✅ Regular method shorthand — 'this' works correctly
incrementCorrect() {
this.count++;
console.log(this.count);
},
};
counter.incrementCorrect(); // 1 ✅
counter.incrementCorrect(); // 2 ✅Mistake 5: Iterating Object with for...of Directly
let user = { name: "Mihir", age: 25, city: "Mumbai" };
// ❌ Objects are NOT iterable directly
for (let val of user) { // TypeError: user is not iterable!
console.log(val);
}
// ✅ Use Object.keys(), Object.values(), or Object.entries()
for (let key of Object.keys(user)) console.log(key);
for (let val of Object.values(user)) console.log(val);
for (let [key, val] of Object.entries(user)) console.log(`${key}: ${val}`);Practical Exercise
Create a file called objects.js:
// 🎯 Objects Practice
// 1. Library book system
console.log("=== 📚 Library System ===");
let library = {
books: [],
members: {},
addBook(title, author, copies = 1) {
this.books.push({ title, author, copies, available: copies });
console.log(`✅ Added: "${title}" by ${author}`);
},
registerMember(id, name) {
this.members[id] = { name, borrowed: [] };
console.log(`👤 Registered: ${name} (ID: ${id})`);
},
borrowBook(memberId, title) {
let book = this.books.find(b => b.title === title);
let member = this.members[memberId];
if (!book) return console.log(`❌ Book not found: "${title}"`);
if (!member) return console.log(`❌ Member not found: ${memberId}`);
if (book.available === 0) return console.log(`❌ "${title}" is not available`);
book.available--;
member.borrowed.push(title);
console.log(`📖 ${member.name} borrowed "${title}"`);
},
returnBook(memberId, title) {
let book = this.books.find(b => b.title === title);
let member = this.members[memberId];
if (!book || !member) return console.log("❌ Invalid return");
book.available++;
member.borrowed = member.borrowed.filter(b => b !== title);
console.log(`🔄 ${member.name} returned "${title}"`);
},
status() {
console.log("\n📦 Book Availability:");
for (let book of this.books) {
console.log(` ${book.title.padEnd(25)} ${book.available}/${book.copies} available`);
}
console.log("\n👥 Member Status:");
for (let [id, member] of Object.entries(this.members)) {
let borrowed = member.borrowed.length > 0 ? member.borrowed.join(", ") : "None";
console.log(` [${id}] ${member.name.padEnd(12)}: ${borrowed}`);
}
},
};
library.addBook("JavaScript: The Good Parts", "Douglas Crockford", 2);
library.addBook("Clean Code", "Robert Martin", 1);
library.addBook("You Don't Know JS", "Kyle Simpson", 3);
library.registerMember("M001", "Mihir");
library.registerMember("M002", "Priya");
library.borrowBook("M001", "Clean Code");
library.borrowBook("M002", "JavaScript: The Good Parts");
library.borrowBook("M001", "You Don't Know JS");
library.borrowBook("M002", "Clean Code"); // Already borrowed
library.status();
library.returnBook("M001", "Clean Code");
library.status();Run it:
node objects.jsExpected Output:
=== 📚 Library System ===
✅ Added: "JavaScript: The Good Parts" by Douglas Crockford
✅ Added: "Clean Code" by Robert Martin
✅ Added: "You Don't Know JS" by Kyle Simpson
👤 Registered: Mihir (ID: M001)
👤 Registered: Priya (ID: M002)
📖 Mihir borrowed "Clean Code"
📖 Priya borrowed "JavaScript: The Good Parts"
📖 Mihir borrowed "You Don't Know JS"
❌ "Clean Code" is not available
📦 Book Availability:
JavaScript: The Good Parts 1/2 available
Clean Code 0/1 available
You Don't Know JS 2/3 available
👥 Member Status:
[M001] Mihir : Clean Code, You Don't Know JS
[M002] Priya : JavaScript: The Good Parts
🔄 Mihir returned "Clean Code"
...Key Takeaways
Congratulations! 🎉 You now have a complete, deep understanding of JavaScript objects.
✅ Objects store related data as key-value pairs — the foundation of structured data in JS.
✅ Dot notation for known keys. Bracket notation for dynamic or special keys.
✅ CRUD on objects:
- Add →
obj.key = value - Read →
obj.keyorobj[key] - Update →
obj.key = newValue - Delete →
delete obj.key
✅ Check existence — use "key" in obj or hasOwnProperty().
✅ Methods — functions as object values. Use this to access the object's own properties. Never use arrow functions as methods.
✅ Built-in helpers:
Object.keys()→ array of keysObject.values()→ array of valuesObject.entries()→ array of[key, value]pairsObject.assign()→ merge objectsObject.freeze()→ make immutableObject.fromEntries()→ pairs array to object
✅ Spread ... — shallow copy and merge objects cleanly.
✅ Optional chaining ?. — safely navigate nested objects without crashing.
Best Practices
- ✅ Use object literals
{}— nevernew Object() - ✅ Use shorthand property names when variable and key match:
{ name, age } - ✅ Use dot notation for known keys, bracket notation for dynamic keys
- ✅ Always use optional chaining
?.when accessing potentially missing nested properties - ✅ Use regular function methods — never arrow functions — when you need
this - ✅ Use spread
...for shallow copies and merges — clean and readable - ✅ Use
JSON.parse(JSON.stringify(obj))for deep copies of plain data objects - ✅ Use
Object.freeze()for configuration objects that should never change - ✅ Use
Object.entries()withfor...ofto loop over objects cleanly - ✅ Keep objects focused — one object should represent one thing
What's Next?
Excellent work! 🎉 You now have a complete understanding of JavaScript objects — one of the most important skills in the language.
Next up, let's learn a powerful modern feature that makes working with objects and arrays much cleaner:
Destructuring: Arrays & Objects → — unpack values from objects and arrays into clean variables in one line!
Let's keep going! 💪