🏗️ Lesson 10: Objects
Arrays store data by position. Objects store data by name — making them perfect for representing real-world things like users, products, and settings.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Create objects using literal syntax
- Read and write properties using dot notation and bracket notation
- Add, update, and delete properties dynamically
- Define methods (functions inside objects)
- Use
thisinside object methods - Nest objects and arrays inside objects
- Destructure objects into variables
- Iterate over object keys, values, and entries
- Check if a property exists and list all keys
Estimated Time: 50 minutes
Project: Build a contact book with search and display features
📑 In This Lesson
Introduction
In the previous lesson you learned that arrays store ordered lists of values accessed by index (a number). Objects take a different approach — they store values accessed by key (a name).
This makes objects ideal for representing things with named attributes. A user has a name, an email, and an age. A product has a title, a price, and a category. Whenever you need to describe what a piece of data means, objects are the tool for the job.
Each piece of data in an object is called a property. A property has a key (the name) and a value (the data). Together, arrays and objects are the two pillars of data handling in JavaScript.
Creating Objects
The standard way to create an object is with curly brace notation (an object literal). Each property is written as key: value, separated by commas.
// Object literal — the standard way
const user = {
name: "Ray",
age: 30,
email: "ray@example.com",
isPro: true
};
// Empty object — add properties later
const settings = {};
// Single-line for small objects
const point = { x: 10, y: 20 };
Property Key Rules
Keys are usually simple names (identifiers), but they can also be strings — which is useful when keys contain spaces or special characters.
const product = {
name: "Laptop", // Simple key — no quotes needed
"price-usd": 999, // Hyphens require quotes
"in stock": true, // Spaces require quotes
123: "numeric key" // Numbers are valid keys (converted to strings)
};
Shorthand Property Names
When the variable name matches the key name, you can use a shorthand.
const name = "Ray";
const age = 30;
const role = "developer";
// Without shorthand
const user1 = { name: name, age: age, role: role };
// With shorthand — cleaner!
const user2 = { name, age, role };
console.log(user2); // { name: "Ray", age: 30, role: "developer" }
Accessing Properties
There are two ways to read a property value: dot notation and bracket notation.
Dot Notation
The most common and readable way. Use when the key is a valid identifier (no spaces, no hyphens, doesn't start with a number).
const car = {
make: "Toyota",
model: "Corolla",
year: 2024
};
console.log(car.make); // "Toyota"
console.log(car.year); // 2024
// Accessing a key that doesn't exist returns undefined
console.log(car.color); // undefined (no error!)
Bracket Notation
Required when the key has special characters, or when the key is stored in a variable.
const product = {
name: "Headphones",
"price-usd": 79,
"in stock": true
};
// Bracket notation with string keys
console.log(product["price-usd"]); // 79
console.log(product["in stock"]); // true
// Dynamic keys — the key comes from a variable
const field = "name";
console.log(product[field]); // "Headphones"
// This is powerful — you can compute which property to access
function getField(obj, key) {
return obj[key];
}
console.log(getField(product, "name")); // "Headphones"
💡 When to Use Which?
- Dot notation — default choice. Clean, readable, and most common.
- Bracket notation — when the key has special characters, or when you need to use a variable as the key.
simple identifier?"} B -->|"Yes"| C["Dot Notation
obj.key"] B -->|"No (spaces, hyphens, etc.)"| D["Bracket Notation
obj['key']"] A --> E{"Is the key stored
in a variable?"} E -->|"Yes"| F["Bracket Notation
obj[variable]"] style C fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style F fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b
Adding, Updating & Deleting
Objects are mutable — you can add new properties, change existing ones, and remove them at any time.
Adding & Updating Properties
const user = {
name: "Ray",
age: 30
};
// Update an existing property
user.age = 31;
// Add a new property — just assign it
user.email = "ray@example.com";
user.isPro = true;
console.log(user);
// { name: "Ray", age: 31, email: "ray@example.com", isPro: true }
Deleting Properties
const settings = {
theme: "dark",
fontSize: 16,
notifications: true,
beta: false
};
// Remove a property entirely
delete settings.beta;
console.log(settings);
// { theme: "dark", fontSize: 16, notifications: true }
console.log(settings.beta); // undefined — it's gone
⚠️ const Doesn't Mean Immutable
Just like arrays, const prevents reassigning the variable but not modifying the object's contents. This is the same behavior you saw in the arrays lesson.
const config = { debug: false };
// ✅ Modifying contents is allowed
config.debug = true;
config.version = "2.0";
// ❌ Reassigning the variable is NOT allowed
// config = { debug: true }; // TypeError
Methods & this
When a property's value is a function, it's called a method. Methods let objects have behavior, not just data.
const calculator = {
// Method — a function as a property value
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
},
multiply(a, b) {
return a * b;
}
};
console.log(calculator.add(5, 3)); // 8
console.log(calculator.multiply(4, 7)); // 28
The this Keyword
Inside a method, this refers to the object that owns the method. It lets a method access other properties on the same object.
const player = {
name: "Ray",
score: 0,
lives: 3,
addPoints(points) {
this.score += points;
console.log(`${this.name} scored ${points}! Total: ${this.score}`);
},
loseLife() {
this.lives--;
console.log(`${this.name} lost a life. Lives left: ${this.lives}`);
},
status() {
return `${this.name}: ${this.score} points, ${this.lives} lives`;
}
};
player.addPoints(10); // "Ray scored 10! Total: 10"
player.addPoints(25); // "Ray scored 25! Total: 35"
player.loseLife(); // "Ray lost a life. Lives left: 2"
console.log(player.status()); // "Ray: 35 points, 2 lives"
❌ Arrow Functions Don't Have Their Own this
Arrow functions inherit this from their surrounding scope, which means they don't work as object methods that need to reference the object.
const broken = {
name: "Oops",
// ❌ Arrow function — 'this' is NOT the object
greet: () => {
console.log(`Hi, I'm ${this.name}`);
},
// ✅ Regular method — 'this' IS the object
greetCorrect() {
console.log(`Hi, I'm ${this.name}`);
}
};
broken.greet(); // "Hi, I'm undefined"
broken.greetCorrect(); // "Hi, I'm Oops"
Nesting Objects & Arrays
Objects can contain other objects, arrays, or any combination. This is how you model real-world data with depth and structure.
const student = {
name: "Maria",
age: 22,
address: {
street: "123 Main St",
city: "Austin",
state: "TX"
},
courses: ["Math 201", "CS 101", "Art 110"],
grades: {
"Math 201": 95,
"CS 101": 88,
"Art 110": 92
}
};
// Accessing nested data — chain the accessors
console.log(student.address.city); // "Austin"
console.log(student.courses[0]); // "Math 201"
console.log(student.grades["CS 101"]); // 88
// Updating nested data
student.address.city = "Dallas";
student.courses.push("History 101");
Arrays of Objects
One of the most common patterns in JavaScript — an array where each element is an object. Think of it like a spreadsheet: each object is a row, each property is a column.
const users = [
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "editor" },
{ id: 3, name: "Carol", role: "viewer" }
];
// Access a specific user
console.log(users[0].name); // "Alice"
// Loop through all users
for (const user of users) {
console.log(`${user.name} is a ${user.role}`);
}
// Find a specific user
const bob = users.find(u => u.name === "Bob");
console.log(bob.role); // "editor"
Destructuring Objects
Just like arrays, you can unpack object properties directly into variables. The difference: with objects, you match by key name instead of position.
const book = {
title: "Dune",
author: "Frank Herbert",
year: 1965,
pages: 412
};
// Without destructuring
const title = book.title;
const author = book.author;
// With destructuring — cleaner!
const { title, author, year, pages } = book;
console.log(title); // "Dune"
console.log(author); // "Frank Herbert"
console.log(year); // 1965
Renaming Variables
const apiResponse = {
user_name: "ray42",
user_email: "ray@example.com"
};
// Rename during destructuring with key: newName
const { user_name: username, user_email: email } = apiResponse;
console.log(username); // "ray42"
console.log(email); // "ray@example.com"
Default Values
const config = { theme: "dark" };
// Set defaults for missing properties
const { theme, fontSize = 16, language = "en" } = config;
console.log(theme); // "dark" (from the object)
console.log(fontSize); // 16 (default — not in object)
console.log(language); // "en" (default — not in object)
Destructuring in Function Parameters
This is one of the most useful patterns — destructure directly in the function signature.
// Without destructuring
function greetUser(user) {
console.log(`Hello, ${user.name}! You are ${user.age} years old.`);
}
// With destructuring — cleaner!
function greetUser({ name, age }) {
console.log(`Hello, ${name}! You are ${age} years old.`);
}
greetUser({ name: "Ray", age: 30, email: "ray@example.com" });
// "Hello, Ray! You are 30 years old."
Iterating Over Objects
Unlike arrays, objects aren't iterable with for...of directly. JavaScript gives you three static methods to work with object data.
Object.keys() — Get All Keys
const car = { make: "Honda", model: "Civic", year: 2023 };
const keys = Object.keys(car);
console.log(keys); // ["make", "model", "year"]
// Loop over keys
for (const key of Object.keys(car)) {
console.log(`${key}: ${car[key]}`);
}
// make: Honda
// model: Civic
// year: 2023
Object.values() — Get All Values
const car = { make: "Honda", model: "Civic", year: 2023 };
const values = Object.values(car);
console.log(values); // ["Honda", "Civic", 2023]
Object.entries() — Get Key-Value Pairs
const car = { make: "Honda", model: "Civic", year: 2023 };
const entries = Object.entries(car);
console.log(entries);
// [["make", "Honda"], ["model", "Civic"], ["year", 2023]]
// Destructure each entry in the loop
for (const [key, value] of Object.entries(car)) {
console.log(`${key} → ${value}`);
}
// make → Honda
// model → Civic
// year → 2023
| Method | Returns | Best For |
|---|---|---|
Object.keys(obj) | Array of key strings | Listing or counting properties |
Object.values(obj) | Array of values | Working with values only |
Object.entries(obj) | Array of [key, value] pairs | Looping with both key and value |
The Spread Operator with Objects
Just like arrays, the spread operator works with objects — perfect for copying or merging.
const defaults = { theme: "light", fontSize: 14, lang: "en" };
const userPrefs = { theme: "dark", fontSize: 18 };
// Merge — later properties override earlier ones
const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: "dark", fontSize: 18, lang: "en" }
// Copy an object
const copy = { ...defaults };
copy.theme = "blue";
console.log(defaults.theme); // "light" — original unchanged
Checking Properties & Comparing
Checking if a Property Exists
const user = { name: "Ray", age: 30, email: undefined };
// The "in" operator — checks if the key exists (even if value is undefined)
console.log("name" in user); // true
console.log("email" in user); // true (key exists!)
console.log("phone" in user); // false
// hasOwnProperty — same idea, method version
console.log(user.hasOwnProperty("age")); // true
console.log(user.hasOwnProperty("phone")); // false
// ⚠️ Be careful with undefined checks
console.log(user.email !== undefined); // false — but the key DOES exist!
console.log(user.phone !== undefined); // false — and this key does NOT exist
// Both return false, so this approach can't distinguish the two cases
Optional Chaining (?.)
When accessing deeply nested properties, you risk errors if an intermediate value is null or undefined. Optional chaining prevents this.
const user = {
name: "Ray",
address: {
city: "Henderson"
}
};
// Without optional chaining — crashes if address is missing
// const zip = user.address.zip; // undefined (OK here)
// const zip = user.work.zip; // ❌ TypeError: Cannot read properties of undefined
// With optional chaining — safe!
console.log(user.address?.city); // "Henderson"
console.log(user.address?.zip); // undefined (no error)
console.log(user.work?.zip); // undefined (no error!)
console.log(user.work?.address?.zip); // undefined (chains safely)
Comparing Objects
❌ Objects Are Compared by Reference, Not Value
Two objects with identical contents are not equal. JavaScript compares whether they're the same object in memory, not whether they have the same data.
const a = { x: 1, y: 2 };
const b = { x: 1, y: 2 };
const c = a;
console.log(a === b); // false — different objects!
console.log(a === c); // true — same reference
// To compare contents, use JSON.stringify (simple objects only)
console.log(JSON.stringify(a) === JSON.stringify(b)); // true
Hands-on Exercise
🏋️ Exercise: Contact Book
Objective: Build a set of functions that manage a contact book using objects and arrays.
// Contact book — an array of contact objects
const contacts = [
{ name: "Alice", phone: "555-0101", email: "alice@example.com", group: "work" },
{ name: "Bob", phone: "555-0202", email: "bob@example.com", group: "family" },
{ name: "Carol", phone: "555-0303", email: "carol@example.com", group: "work" }
];
// TODO: Write these functions:
// 1. addContact(book, contact)
// - Add a contact object to the book
// - Don't add if a contact with the same name already exists
// - Return true if added, false if duplicate
// 2. findContact(book, name)
// - Find and return the contact with the given name
// - Return null if not found
// 3. updateContact(book, name, updates)
// - Find the contact by name, then apply the updates object
// - Example: updateContact(contacts, "Alice", { phone: "555-9999" })
// - Return true if updated, false if contact not found
// 4. displayContact(contact)
// - Log a formatted display of the contact:
// "Name: Alice | Phone: 555-0101 | Email: alice@example.com | Group: work"
// 5. listByGroup(book, group)
// - Return an array of names of contacts in the given group
// Test your functions:
// addContact(contacts, { name: "Dave", phone: "555-0404", email: "dave@example.com", group: "family" });
// console.log(findContact(contacts, "Bob"));
// updateContact(contacts, "Alice", { phone: "555-9999", group: "friend" });
// displayContact(findContact(contacts, "Alice"));
// console.log(listByGroup(contacts, "work"));
💡 Hint
addContact: Use find() to check for duplicates by name first, then push() if safe.
findContact: Use find() with a name comparison. Return null instead of undefined for clarity.
updateContact: Find the contact first. Then use Object.assign(contact, updates) or the spread operator to apply changes.
displayContact: Use Object.entries() or template literals to format the output.
listByGroup: Use filter() to get matching contacts, then map() to extract just the names.
✅ Solution
const contacts = [
{ name: "Alice", phone: "555-0101", email: "alice@example.com", group: "work" },
{ name: "Bob", phone: "555-0202", email: "bob@example.com", group: "family" },
{ name: "Carol", phone: "555-0303", email: "carol@example.com", group: "work" }
];
function addContact(book, contact) {
const exists = book.find(c => c.name === contact.name);
if (exists) {
console.log(`Contact "${contact.name}" already exists.`);
return false;
}
book.push(contact);
return true;
}
function findContact(book, name) {
const contact = book.find(c => c.name === name);
return contact || null;
}
function updateContact(book, name, updates) {
const contact = findContact(book, name);
if (!contact) {
console.log(`Contact "${name}" not found.`);
return false;
}
Object.assign(contact, updates);
return true;
}
function displayContact(contact) {
if (!contact) {
console.log("No contact to display.");
return;
}
const parts = Object.entries(contact)
.map(([key, value]) => `${key[0].toUpperCase() + key.slice(1)}: ${value}`)
.join(" | ");
console.log(parts);
}
function listByGroup(book, group) {
return book
.filter(c => c.group === group)
.map(c => c.name);
}
// Testing
addContact(contacts, {
name: "Dave", phone: "555-0404",
email: "dave@example.com", group: "family"
});
// Added successfully
addContact(contacts, {
name: "Alice", phone: "555-0000",
email: "new@example.com", group: "other"
});
// Contact "Alice" already exists.
console.log(findContact(contacts, "Bob"));
// { name: "Bob", phone: "555-0202", ... }
updateContact(contacts, "Alice", { phone: "555-9999", group: "friend" });
displayContact(findContact(contacts, "Alice"));
// Name: Alice | Phone: 555-9999 | Email: alice@example.com | Group: friend
console.log(listByGroup(contacts, "family"));
// ["Bob", "Dave"]
🎯 Quick Quiz
Question 1: Which notation must you use when the key is stored in a variable?
Question 2: What does Object.keys({ a: 1, b: 2, c: 3 }) return?
Question 3: Why shouldn't you use arrow functions as object methods that use this?
Question 4: What does { x: 1 } === { x: 1 } evaluate to?
Summary
🎉 Key Takeaways
- Objects store data as key-value pairs — perfect for named properties
- Dot notation (
obj.key) is the default; bracket notation (obj["key"]) is for dynamic or special keys - Objects are mutable — add, update, or delete properties freely
- Methods are functions inside objects; use
thisto reference the object (avoid arrow functions for methods) - Nesting objects and arrays models complex, real-world data
- Destructuring unpacks properties into variables — especially useful in function parameters
Object.keys(),Object.values(), andObject.entries()let you iterate over objects- Optional chaining (
?.) safely accesses nested properties without crashes - Objects are compared by reference, not by value
📚 Additional Resources
- MDN — Working with Objects
- javascript.info — Objects
- MDN — Destructuring Assignment
- MDN — Optional Chaining
🚀 What's Next?
You now know both fundamental data structures — arrays and objects. In the next lesson, you'll learn array methods like map, filter, and reduce — powerful tools that transform data cleanly and concisely without writing manual loops.