JavaScript Tutorial

Objects in JavaScript: Complete Guide with Examples

Master JavaScript objects — creating, reading, updating, deleting properties, methods, computed keys, Object.keys, Object.values, Object.entries, object spread, cloning, and real-world examples with best practices.

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 readable

Accessing 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 — ❌ SyntaxError

Dot 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 fragile

Object 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)); // 50

this 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"]); // 88

Deep 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); // 50

Built-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: Mumbai

Object.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 safely

Real-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 | editor

Example 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 products

Example 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.1

Common 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.js

Expected 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.key or obj[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 keys
  • Object.values() → array of values
  • Object.entries() → array of [key, value] pairs
  • Object.assign() → merge objects
  • Object.freeze() → make immutable
  • Object.fromEntries() → pairs array to object

Spread ... — shallow copy and merge objects cleanly.

Optional chaining ?. — safely navigate nested objects without crashing.


Best Practices

  1. ✅ Use object literals {} — never new Object()
  2. ✅ Use shorthand property names when variable and key match: { name, age }
  3. ✅ Use dot notation for known keys, bracket notation for dynamic keys
  4. ✅ Always use optional chaining ?. when accessing potentially missing nested properties
  5. ✅ Use regular function methods — never arrow functions — when you need this
  6. ✅ Use spread ... for shallow copies and merges — clean and readable
  7. ✅ Use JSON.parse(JSON.stringify(obj)) for deep copies of plain data objects
  8. ✅ Use Object.freeze() for configuration objects that should never change
  9. ✅ Use Object.entries() with for...of to loop over objects cleanly
  10. ✅ 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! 💪