Skip to main content

Objects

Introduction​

Objects are one of the most important concepts in programming. They let you group related data and functionality together into a single unit. Think of an object as a container that holds information (properties) and actions (methods) related to a real-world entity.

For example, a person object might have:

  • Properties : name, age, email (data)
  • Methods : greet(), updateProfile() (actions)

In TypeScript, objects become even more powerful because you can define the exact structure an object should have. This prevents bugs where you accidentally access properties that don't exist or assign the wrong type of value.

Why Objects Matter​

Objects help you organize complex data:

Without objects (messy):

let userName: string = "Alice";
let userAge: number = 25;
let userEmail: string = "alice@email.com";
let userCity: string = "London";
// What if you need to pass this data around? Pass 4 separate variables?

With objects (organized):

let user = {
name: "Alice",
age: 25,
email: "alice@email.com",
city: "London"
};
// Pass one object that contains everything!

TypeScript's Object Type Safety​

The major difference in TypeScript is that you can define the shape of your objects:

JavaScript (no type checking):

let user = { name: "Alice", age: 25 };
console.log(user.email); // undefined - typo! Should be checking if it exists
user.age = "twenty-five"; // Oops! Changed type from number to string

TypeScript (with type checking):

interface User {
name: string;
age: number;
email?: string; // Optional property
}

let user: User = { name: "Alice", age: 25 };
// console.log(user.emial); // ❌ Error: Property 'emial' does not exist (catches typo!)
// user.age = "twenty-five"; // ❌ Error: Type 'string' is not assignable to type 'number'

Creating Objects​

Object Literals​

The most common way to create objects:

let person = {
name: "Alice",
age: 25,
city: "London"
};

console.log(person); // { name: 'Alice', age: 25, city: 'London' }

With Type Annotations​

You can specify the object's shape:

let person: { name: string; age: number; city: string } = {
name: "Alice",
age: 25,
city: "London"
};

// TypeScript checks this matches!
// let invalid: { name: string; age: number } = {
// name: "Bob" // ❌ Error: Property 'age' is missing
// };

Using Interfaces​

For reusable object shapes (recommended for complex objects):

interface Person {
name: string;
age: number;
city: string;
}

let alice: Person = {
name: "Alice",
age: 25,
city: "London"
};

let bob: Person = {
name: "Bob",
age: 30,
city: "Paris"
};

Using Type Aliases​

Similar to interfaces:

type Person = {
name: string;
age: number;
city: string;
};

let person: Person = {
name: "Alice",
age: 25,
city: "London"
};

When to use Interface vs Type?

  • Use interface for object shapes (more extensible)
  • Use type for unions, primitives, or complex types
  • Either works for simple objects - be consistent!

Accessing Properties​

Dot Notation​

The most common way:

let person = {
name: "Alice",
age: 25
};

console.log(person.name); // "Alice"
console.log(person.age); // 25

Bracket Notation​

Useful for dynamic property names or special characters:

let person = {
name: "Alice",
"favorite color": "blue" // Space in key
};

// Must use brackets for special characters
console.log(person["favorite color"]); // "blue"

// Dynamic property access
let propertyName: string = "name";
console.log(person[propertyName]); // "Alice"

// With variables
let key: string = "age";
// console.log(person[key]); // Would work if person had 'age'

Type Safety with Property Access​

TypeScript ensures properties exist:

interface User {
name: string;
age: number;
}

let user: User = { name: "Alice", age: 25 };

console.log(user.name); // ✅ "Alice"
// console.log(user.email); // ❌ Error: Property 'email' does not exist

Optional Properties​

Mark properties as optional with ?:

interface User {
name: string;
age: number;
email?: string; // Optional
phone?: string; // Optional
}

// Valid - optional properties can be omitted
let user1: User = {
name: "Alice",
age: 25
};

// Also valid - optional properties can be included
let user2: User = {
name: "Bob",
age: 30,
email: "bob@email.com"
};

Accessing Optional Properties Safely​

interface User {
name: string;
email?: string;
}

let user: User = { name: "Alice" };

// Safe access with optional chaining
console.log(user.email?.toLowerCase()); // undefined (no error!)

// Check before using
if (user.email) {
console.log(user.email.toLowerCase());
}

// With default value
let email: string = user.email ?? "No email provided";

Adding and Modifying Properties​

Adding Properties​

interface Person {
name: string;
age: number;
city?: string; // Optional so we can add it later
}

let person: Person = {
name: "Alice",
age: 25
};

person.city = "London"; // ✅ Add optional property
console.log(person); // { name: 'Alice', age: 25, city: 'London' }

// person.country = "UK"; // ❌ Error: Property 'country' does not exist

Modifying Properties​

let person = {
name: "Alice",
age: 25
};

person.age = 26; // ✅ Modify property
console.log(person.age); // 26

// person.age = "twenty-six"; // ❌ Error: Type 'string' not assignable

Dynamic Properties with Index Signatures​

For objects with dynamic keys:

interface StringMap {
[key: string]: string;
}

let translations: StringMap = {};
translations["hello"] = "hola";
translations["goodbye"] = "adiós";
translations["thanks"] = "gracias";

console.log(translations["hello"]); // "hola"

// More specific example
interface UserScores {
[username: string]: number;
}

let scores: UserScores = {
alice: 100,
bob: 85,
charlie: 92
};

Readonly Properties​

Prevent modification after creation:

interface User {
readonly id: number;
name: string;
age: number;
}

let user: User = {
id: 1,
name: "Alice",
age: 25
};

user.name = "Alicia"; // ✅ Can modify
// user.id = 2; // ❌ Error: Cannot assign to 'id' because it is a read-only property

Methods​

Objects can contain functions (methods):

interface Person {
name: string;
age: number;
greet: () => string;
}

let person: Person = {
name: "Alice",
age: 25,
greet: function() {
return `Hello, I'm ${this.name}!`;
}
};

console.log(person.greet()); // "Hello, I'm Alice!"

// Shorthand method syntax (preferred)
let person2 = {
name: "Bob",
age: 30,
greet() {
return `Hi, I'm ${this.name}!`;
}
};

The this Keyword​

this refers to the object the method belongs to:

let calculator = {
value: 0,
add(n: number) {
this.value += n;
return this; // Return this for chaining
},
subtract(n: number) {
this.value -= n;
return this;
},
getValue() {
return this.value;
}
};

calculator.add(10).add(5).subtract(3);
console.log(calculator.getValue()); // 12

// Method chaining works!

Arrow Functions and this​

Arrow functions don't have their own this:

let person = {
name: "Alice",
greet: function() {
console.log(`Hello, ${this.name}`); // ✅ 'this' works
},
arrowGreet: () => {
// console.log(`Hello, ${this.name}`); // ❌ 'this' is undefined!
}
};

// Use regular functions for methods that need 'this'

Deleting Properties​

Remove properties with delete:

let person = {
name: "Alice",
age: 25,
city: "London"
};

delete person.city;
console.log(person); // { name: 'Alice', age: 25 }

// Check if property exists
console.log("city" in person); // false

Nested Objects​

Objects can contain other objects:

interface Address {
street: string;
city: string;
country: string;
}

interface Person {
name: string;
age: number;
address: Address;
}

let person: Person = {
name: "Alice",
age: 25,
address: {
street: "123 Main St",
city: "London",
country: "UK"
}
};

// Access nested properties
console.log(person.address.city); // "London"
console.log(person.address.country); // "UK"

// Optional chaining with nested objects
interface User {
name: string;
address?: {
city?: string;
};
}

let user: User = { name: "Bob" };
console.log(user.address?.city); // undefined (no error!)

Object Destructuring​

Extract properties into variables:

let person = {
name: "Alice",
age: 25,
city: "London"
};

// Destructuring
let { name, age, city } = person;
console.log(name); // "Alice"
console.log(age); // 25
console.log(city); // "London"

// Rename during destructuring
let { name: personName, age: personAge } = person;
console.log(personName); // "Alice"

// Default values
let { name, country = "Unknown" } = person;
console.log(country); // "Unknown"

// Nested destructuring
let user = {
name: "Bob",
address: {
city: "Paris",
country: "France"
}
};

let { name, address: { city, country } } = user;
console.log(city); // "Paris"
console.log(country); // "France"

Object Spreading​

Copy and merge objects:

let person = {
name: "Alice",
age: 25
};

// Copy object
let personCopy = { ...person };
console.log(personCopy); // { name: 'Alice', age: 25 }

// Add properties
let extendedPerson = { ...person, city: "London" };
console.log(extendedPerson); // { name: 'Alice', age: 25, city: 'London' }

// Merge objects (later properties override earlier ones)
let defaults = { theme: "light", fontSize: 14 };
let userSettings = { fontSize: 16, language: "en" };
let settings = { ...defaults, ...userSettings };
console.log(settings); // { theme: 'light', fontSize: 16, language: 'en' }

Object Methods (Built-in)​

Object.keys()​

Get array of property names:

let person = {
name: "Alice",
age: 25,
city: "London"
};

let keys: string[] = Object.keys(person);
console.log(keys); // ["name", "age", "city"]

Object.values()​

Get array of property values:

let values: any[] = Object.values(person);
console.log(values); // ["Alice", 25, "London"]

Object.entries()​

Get array of [key, value] pairs:

let entries: [string, any][] = Object.entries(person);
console.log(entries);
// [["name", "Alice"], ["age", 25], ["city", "London"]]

// Useful for iteration
for (let [key, value] of entries) {
console.log(`${key}: ${value}`);
}

Object.assign()​

Copy properties from source to target:

let target = { name: "Alice" };
let source = { age: 25, city: "London" };

Object.assign(target, source);
console.log(target); // { name: 'Alice', age: 25, city: 'London' }

// Spread is usually preferred
let merged = { ...target, ...source };

Type Aliases vs Interfaces​

Both define object shapes, but have differences:

Interfaces​

interface Person {
name: string;
age: number;
}

// Can extend
interface Employee extends Person {
employeeId: string;
department: string;
}

let emp: Employee = {
name: "Alice",
age: 25,
employeeId: "E123",
department: "Engineering"
};

// Can merge (declaration merging)
interface User {
name: string;
}

interface User {
age: number;
}

// User now has both name and age

Type Aliases​

type Person = {
name: string;
age: number;
};

// Can use with unions
type ID = string | number;

// Can use with intersections
type Employee = Person & {
employeeId: string;
department: string;
};

Recommendation: Use interfaces for object shapes, types for everything else.


Concepts​

Quick Reference​

// Creating objects
let obj = { key: "value" };

// With type annotation
let person: { name: string; age: number } = {
name: "Alice",
age: 25
};

// With interface
interface User {
name: string;
age: number;
email?: string; // Optional
readonly id: number; // Readonly
}

// Accessing properties
obj.key // Dot notation
obj["key"] // Bracket notation

// Methods
let calc = {
value: 0,
add(n: number) {
this.value += n;
}
};

// Destructuring
let { name, age } = person;

// Spreading
let copy = { ...person };
let merged = { ...obj1, ...obj2 };

// Object methods
Object.keys(obj) // ["key"]
Object.values(obj) // ["value"]
Object.entries(obj) // [["key", "value"]]

Examples​

// User profile system
interface UserProfile {
id: number;
username: string;
email: string;
preferences: {
theme: "light" | "dark";
notifications: boolean;
};
getDisplayName: () => string;
}

let user: UserProfile = {
id: 1,
username: "alice_dev",
email: "alice@example.com",
preferences: {
theme: "dark",
notifications: true
},
getDisplayName() {
return `@${this.username}`;
}
};

// Product catalog
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
calculateDiscount: (percent: number) => number;
}

let laptop: Product = {
id: "LAP001",
name: "Gaming Laptop",
price: 1200,
inStock: true,
calculateDiscount(percent: number) {
return this.price * (1 - percent / 100);
}
};

console.log(laptop.calculateDiscount(10)); // 1080

Challenges​

1. Create Student Object​

  • Create an object student with properties: name, age, and course.
  • Use proper TypeScript type annotations.
  • Print each property individually.
💡 Show Answer
interface Student {
name: string;
age: number;
course: string;
}

let student: Student = {
name: "Alice",
age: 21,
course: "Computer Science"
};

console.log(student.name); // "Alice"
console.log(student.age); // 21
console.log(student.course); // "Computer Science"

// Alternative with type alias
type Student2 = {
name: string;
age: number;
course: string;
};

// Function to create student
function createStudent(name: string, age: number, course: string): Student {
return { name, age, course };
}

let bob = createStudent("Bob", 22, "Engineering");
console.log(bob);


2. Add and Update Properties​

  • Add a new property grade to the student object.
  • Update the student's age.
  • Make grade an optional property in the type definition.
💡 Show Answer
interface Student {
name: string;
age: number;
course: string;
grade?: string; // Optional property
}

let student: Student = {
name: "Alice",
age: 21,
course: "Computer Science"
};

// Add grade
student.grade = "A";
console.log(student.grade); // "A"

// Update age
student.age = 22;
console.log(student.age); // 22

console.log(student);
// { name: 'Alice', age: 22, course: 'Computer Science', grade: 'A' }

// Function to update student
function updateStudentAge(student: Student, newAge: number): void {
student.age = newAge;
}

updateStudentAge(student, 23);
console.log(student.age); // 23

3. Delete Property​

  • Create a student object with an isGraduate property.
  • Delete the isGraduate property.
  • Verify it's gone using in operator or Object.keys().
💡 Show Answer
interface Student {
name: string;
age: number;
isGraduate?: boolean;
}

let student: Student = {
name: "Alice",
age: 21,
isGraduate: false
};

console.log("Before delete:", student);
// { name: 'Alice', age: 21, isGraduate: false }

delete student.isGraduate;

console.log("After delete:", student);
// { name: 'Alice', age: 21 }

// Verify deletion
console.log("isGraduate" in student); // false
console.log(Object.keys(student)); // ["name", "age"]

// Safe check before access
if (student.isGraduate !== undefined) {
console.log("Is graduate:", student.isGraduate);
} else {
console.log("Graduate status unknown");
}


4. Add Method getSummary​

  • Create a Book interface with title and author.
  • Add a method getSummary() that returns a formatted string.
  • Create a book object and call the method.
💡 Show Answer
interface Book {
title: string;
author: string;
year?: number;
getSummary: () => string;
}

let book: Book = {
title: "Harry Potter and the Philosopher's Stone",
author: "J.K. Rowling",
year: 1997,
getSummary() {
if (this.year) {
return `"${this.title}" by ${this.author} (${this.year})`;
}
return `"${this.title}" by ${this.author}`;
}
};

console.log(book.getSummary());
// "Harry Potter and the Philosopher's Stone" by J.K. Rowling (1997)

// Array of books
let library: Book[] = [
{
title: "1984",
author: "George Orwell",
getSummary() {
return `"${this.title}" by ${this.author}`;
}
},
{
title: "To Kill a Mockingbird",
author: "Harper Lee",
getSummary() {
return `"${this.title}" by ${this.author}`;
}
}
];

library.forEach(book => console.log(book.getSummary()));

5. Bracket Notation with Special Keys​

  • Create an object with a property name that contains a space.
  • Access it using bracket notation.
  • Demonstrate why dot notation won't work.
💡 Show Answer
interface Person {
name: string;
age: number;
"favorite color": string; // Key with space
"home address"?: string;
}

let person: Person = {
name: "Alice",
age: 25,
"favorite color": "blue"
};

// Access with bracket notation
console.log(person["favorite color"]); // "blue"

// Dot notation won't work
// console.log(person.favorite color); // ❌ Syntax error

// Dynamic key access
let key: string = "favorite color";
console.log(person[key]); // "blue"

// Adding property with space
person["home address"] = "123 Main St";
console.log(person["home address"]);

// Index signature for flexible keys
interface FlexibleObject {
[key: string]: string | number | boolean;
name: string; // Required property
}

let obj: FlexibleObject = {
name: "Test",
"property with space": "value",
"another-property": 123
};


6. Object Destructuring​

  • Create a car object with make, year, and color.
  • Use destructuring to extract all properties.
  • Rename a property during destructuring.
  • Provide default values for missing properties.
💡 Show Answer
interface Car {
make: string;
year: number;
color: string;
owner?: string;
}

let car: Car = {
make: "Toyota",
year: 2020,
color: "Blue"
};

// Basic destructuring
let { make, year, color } = car;
console.log(make); // "Toyota"
console.log(year); // 2020
console.log(color); // "Blue"

// Rename during destructuring
let { make: carMake, year: carYear } = car;
console.log(carMake); // "Toyota"
console.log(carYear); // 2020

// Default values
let { make: m, owner = "Unknown" } = car;
console.log(m); // "Toyota"
console.log(owner); // "Unknown"

// Destructuring in function parameters
function displayCar({ make, year, color }: Car): string {
return `${year} ${make} (${color})`;
}

console.log(displayCar(car)); // "2020 Toyota (Blue)"

// Rest operator in destructuring
let { make: carBrand, ...rest } = car;
console.log(carBrand); // "Toyota"
console.log(rest); // { year: 2020, color: 'Blue' }

7. Nested Objects​

  • Create a company object with nested address object.
  • Create a proper interface for the nested structure.
  • Access nested properties safely.
💡 Show Answer
interface Address {
street: string;
city: string;
country: string;
zipCode?: string;
}

interface Company {
name: string;
founded: number;
address: Address;
employees?: number;
}

let company: Company = {
name: "TechCorp",
founded: 2010,
address: {
street: "123 Tech Street",
city: "San Francisco",
country: "USA",
zipCode: "94105"
},
employees: 500
};

// Access nested properties
console.log(company.name); // "TechCorp"
console.log(company.address.city); // "San Francisco"
console.log(company.address.country); // "USA"

// Nested destructuring
let { name, address: { city, country } } = company;
console.log(`${name} is in ${city}, ${country}`);
// "TechCorp is in San Francisco, USA"

// Optional chaining with nested objects
interface User {
name: string;
contact?: {
email?: string;
address?: {
city?: string;
};
};
}

let user: User = { name: "Bob" };
console.log(user.contact?.address?.city); // undefined (no error!)

// Deep nesting example
let library = {
name: "City Library",
sections: {
fiction: {
books: [
{ title: "1984", author: "George Orwell" },
{ title: "Brave New World", author: "Aldous Huxley" }
]
},
nonFiction: {
books: [
{ title: "Sapiens", author: "Yuval Noah Harari" }
]
}
}
};

console.log(library.sections.fiction.books[0].title); // "1984"


8. Count Properties​

  • Write a function that counts the number of properties in an object.
  • Make it type-safe with generics.
  • Handle nested objects differently if needed.
💡 Show Answer
// Basic version
function countProperties(obj: object): number {
return Object.keys(obj).length;
}

let student = {
name: "Alice",
age: 21,
course: "Computer Science",
grade: "A"
};

console.log(countProperties(student)); // 4

// Generic version
function countPropertiesGeneric<T extends object>(obj: T): number {
return Object.keys(obj).length;
}

console.log(countPropertiesGeneric(student)); // 4

// Count only own properties (not inherited)
function countOwnProperties(obj: object): number {
return Object.getOwnPropertyNames(obj).length;
}

// Count nested properties recursively
function countAllProperties(obj: any): number {
let count = 0;
for (let key in obj) {
count++;
if (typeof obj[key] === "object" && obj[key] !== null) {
count += countAllProperties(obj[key]);
}
}
return count;
}

let nested = {
name: "Alice",
address: {
city: "London",
country: "UK"
}
};

console.log(countProperties(nested)); // 2 (name, address)
console.log(countAllProperties(nested)); // 4 (name, address, city, country)

// With type information
function analyzeObject<T extends object>(obj: T): {
propertyCount: number;
properties: string[];
} {
const properties = Object.keys(obj);
return {
propertyCount: properties.length,
properties
};
}

console.log(analyzeObject(student));
// { propertyCount: 4, properties: ['name', 'age', 'course', 'grade'] }

9. Loop Through Object​

  • Use for...in to loop through object properties.
  • Use Object.entries() for a more modern approach.
  • Create a function to display all key-value pairs.
💡 Show Answer
interface Student {
name: string;
age: number;
course: string;
isGraduate: boolean;
}

let student: Student = {
name: "Alice",
age: 21,
course: "Computer Science",
isGraduate: false
};

// Using for...in
console.log("Using for...in:");
for (let key in student) {
console.log(`${key}: ${student[key as keyof Student]}`);
}

// Using Object.entries() (preferred)
console.log("\nUsing Object.entries():");
for (let [key, value] of Object.entries(student)) {
console.log(`${key}: ${value}`);
}

// Using Object.keys()
console.log("\nUsing Object.keys():");
Object.keys(student).forEach(key => {
console.log(`${key}: ${student[key as keyof Student]}`);
});

// Function to display object
function displayObject `<T extends object>`(obj: T, title?: string): void {
if (title) console.log(`\n${title}:`);
Object.entries(obj).forEach(([key, value]) => {
console.log(` ${key}: ${value}`);
});
}

displayObject(student, "Student Information");

// Filter properties
function getStringProperties `<T extends object>`(obj: T): Record<string, string> {
const result: Record<string, string> = {};
for (let [key, value] of Object.entries(obj)) {
if (typeof value === "string") {
result[key] = value;
}
}
return result;
}

console.log(getStringProperties(student));
// { name: 'Alice', course: 'Computer Science' }

// Map object properties
function mapObjectValues `<T extends object>`(
obj: T,
fn: (value: any) => any
): Record<string, any> {
const result: Record<string, any> = {};
for (let [key, value] of Object.entries(obj)) {
result[key] = fn(value);
}
return result;
}

let doubled = mapObjectValues({ a: 1, b: 2, c: 3 }, x => x * 2);
console.log(doubled); // { a: 2, b: 4, c: 6 }


10. Merge Objects with Spread​

  • Merge two objects using the spread operator.
  • Handle property conflicts (same property in both objects).
  • Create a function that merges objects with conflict resolution.
💡 Show Answer
interface ContactInfo {
name: string;
email: string;
phone: string;
}

interface PersonalInfo {
name: string;
address: string;
city: string;
country: string;
}

let contactInfo: ContactInfo = {
name: "Alice Johnson",
email: "alice@email.com",
phone: "555-0100"
};

let personalInfo: PersonalInfo = {
name: "Alice J.", // Different name - will override
address: "123 Main St",
city: "London",
country: "UK"
};

// Simple merge (later values override)
let merged1 = { ...contactInfo, ...personalInfo };
console.log(merged1);
// name is "Alice J." from personalInfo

// Reverse order
let merged2 = { ...personalInfo, ...contactInfo };
console.log(merged2);
// name is "Alice Johnson" from contactInfo

// Nested merge strategy
interface MergedInfo {
contact: ContactInfo;
personal: PersonalInfo;
}

let structuredMerge: MergedInfo = {
contact: { ...contactInfo },
personal: { ...personalInfo }
};
console.log(structuredMerge);
// Both objects preserved with their own names

// Custom merge function
function mergeObjects<T extends object, U extends object>(
obj1: T,
obj2: U,
strategy: "first" | "second" | "nested" = "second"
): any {
if (strategy === "nested") {
return {
first: { ...obj1 },
second: { ...obj2 }
};
} else if (strategy === "first") {
return { ...obj2, ...obj1 }; // obj1 wins conflicts
} else {
return { ...obj1, ...obj2 }; // obj2 wins conflicts
}
}

console.log(mergeObjects(contactInfo, personalInfo, "first"));
// name is "Alice Johnson"

console.log(mergeObjects(contactInfo, personalInfo, "nested"));
// Preserves both in separate properties

// Deep merge function
function deepMerge<T extends object>(target: T, source: Partial<T>): T {
const result = { ...target };
for (let key in source) {
if (source[key] !== undefined) {
result[key] = source[key]!;
}
}
return result;
}

// Merge multiple objects
function mergeMultiple<T extends object>(...objects: Partial<T>[]): T {
return objects.reduce((acc, obj) => ({ ...acc, ...obj }), {} as T);
}

let settings1 = { theme: "dark", fontSize: 14 };
let settings2 = { fontSize: 16, language: "en" };
let settings3 = { notifications: true };

let allSettings = mergeMultiple(settings1, settings2, settings3);
console.log(allSettings);
// { theme: 'dark', fontSize: 16, language: 'en', notifications: true }

Summary​

In this chapter, you learned:

✅ How to create objects with object literals and type annotations

✅ Defining object shapes with interfaces and type aliases

✅ Accessing properties with dot and bracket notation

✅ Optional and readonly properties

✅ Adding, modifying, and deleting properties

✅ Creating methods inside objects

✅ Understanding the this keyword

✅ Nested objects and safe property access

✅ Object destructuring for cleaner code

✅ Spreading objects for copying and merging

✅ Built-in object methods (keys, values, entries)

Key Takeaways:

  • Objects group related data and behavior together
  • TypeScript's interfaces and types define object shapes for safety
  • Use optional properties with ? for flexibility
  • Use readonly for properties that shouldn't change
  • Dot notation for known properties, bracket notation for dynamic access
  • Methods let objects have behavior, not just data
  • Destructuring and spreading make object manipulation cleaner
  • Optional chaining (?.) prevents errors with nested objects

Best Practices:

  • Use interfaces for object shapes (more extensible than types)
  • Make properties optional only when they truly are optional
  • Use readonly for IDs and other immutable data
  • Prefer destructuring for cleaner function parameters
  • Use the spread operator for shallow copying
  • Use descriptive property names
  • Group related data in nested objects
  • Validate object shapes at boundaries (API responses, user input)

In the next chapter, we'll explore JSON and learn how to work with data interchange formats in a type-safe way!

Continue to Chapter 7: JSON →