Programming Fundamentals

Objects - Structured Data

3 min read
Focus: PROGRAMMING-FUNDAMENTALS

TL;DR — Quick Summary

  • Use dot notation for known keys, bracket notation for dynamic or computed keys.
  • Destructuring extracts properties into variables — use it in function parameters to document what you need from an object.
  • Spread ({ ...obj }) does a shallow copy — nested objects are still shared by reference.
  • Object.entries() + for...of is the clean modern way to iterate an object's key-value pairs.

Lesson Overview

Objects are the foundation of JavaScript — almost everything in JS is an object or behaves like one. An object groups related data and behaviour under one name: a user object has a name, an email, a login() method. This mirrors how we think about real-world entities.

Objects use key-value pairs: keys are strings (or Symbols), values can be anything — primitives, arrays, other objects, or functions. When a function is a value in an object, it's called a method.

Modern JavaScript adds powerful syntax: shorthand properties, computed keys, destructuring, spread/rest, and optional chaining — all essential for working with objects in real codebases.

Conceptual Deep Dive

Creating objects:

  • Object literal: const user = { name: 'Alice', age: 30 }
  • Constructor function: function User(name) { this.name = name; }
  • Class: class User { constructor(name) { this.name = name; } }
  • Object.create(proto): creates with specific prototype

Accessing and modifying:

  • Dot notation: user.name
  • Bracket notation: user['name'] or user[dynamicKey]
  • Optional chaining: user?.address?.city

Object methods:

  • Object.keys(obj): array of keys
  • Object.values(obj): array of values
  • Object.entries(obj): array of [key, value] pairs
  • Object.assign(target, source): shallow merge
  • Object.freeze(obj): make immutable
  • { ...obj }: spread (shallow copy/merge)

Implementation Lab

Object Fundamentals
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
31
32
33
34
35
36
37
// Object literal
const product = {
  id: 42,
  name: 'Mechanical Keyboard'Keyboard',
  price: 149.99,
  inStock: true,
  tags: ['tech', 'productivity'],
  // Method
  getDisplayPrice() {
    return `$${this.price.toFixed(2)}`this.price.toFixed(2)}`;
  },
  // Arrow function doesn't work for 'this'function doesn't work for 'this'
};
 
// Access
console.log(product.name);           // dot notation
console.log(product['price']);        // bracket notation (same result))
const key = 'inStock';
console.log(product[key]);            // dynamic key access — must use brackets
 
// Modify
product.price = 129.99;              // update existing
product.discount = 0.1;              // add new property
delete product.discount;             // remove property
 
// Methods
console.log(product.getDisplayPrice()); // '$149.99'
 
// Shorthand property syntax
const name = 'Alice';
const age = 30;
const user = { name, age }; // same as { name: name, age: age }}
 
// Computed property keys
const field = 'score';
const gameData = { [field]: 100, [`${field}History`}History`]: [] };
console.log(gameData.score); // 100
Destructuring, Spread, and Object Methods
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  address: { city: 'London', country: 'UK' },
  role: 'admin'
};
 
// Destructuring: unpack properties into variables
const { name, email, role } = user;
console.log(name, email); // 'Alice' 'alice@example.com''alice@example.com'
 
// Rename during destructuring
const { name: userName, role: userRole = 'viewer' } = user;
console.log(userName); // 'Alice'
 
// Nested destructuring
const { address: { city, country } } = user;
console.log(city); // 'London'
 
// Rest in destructuring
const { id, ...publicData } = user;
console.log(publicData); // { name, email, address, role } — no id} — no id
 
// Spread: shallow copy and merge
const updated = { ...user, role: 'editor', lastSeen: new Date() };
// user is unchanged, updated has all user's props + overrides
 
// Merge two objects
const defaults = { theme: 'dark', language: 'en', fontSize: 14 };
const userPrefs = { theme: 'light', fontSize: 16 };
const settings = { ...defaults, ...userPrefs }; // userPrefs overrides defaults
console.log(settings); // { theme: 'light', language: 'en', fontSize: 16 }'light', language: 'en', fontSize: 16 }
 
// Object.entries + destructuring = elegant iteration
const scores = { alice: 95, bob: 78, charlie: 88 };
for (const [player, score] of Object.entries(scores)) {
  console.log(`${player}: ${score}`}: ${score}`);
}
 
// Object.fromEntries: array of pairs → object (inverse of entries)(inverse of entries)
const doubled = Object.fromEntries(
  Object.entries(scores).map(([k, v]) => [k, v * 2])
);
console.log(doubled); // { alice: 190, bob: 156, charlie: 176 }190, bob: 156, charlie: 176 }

Pro Tips — Senior Dev Insights

1

Use structuredClone(obj) (modern) or JSON.parse(JSON.stringify(obj)) (legacy) for deep copies when you need complete independence from the original.

2

Object.freeze() makes an object shallow-immutable — useful for constants like config and enums: const ROLES = Object.freeze({ ADMIN: 'admin', EDITOR: 'editor' }).

3

Destructure in function parameters to make dependencies explicit and enable default values: function createUser({ name, role = 'viewer', active = true } = {}).

Common Developer Pitfalls

!
Assuming spread creates a deep copy — { ...obj } only copies the top level. Nested objects are copied by reference: changing a nested property affects both copies. Use structuredClone(obj) for deep copies.
!
Using arrow functions as object methods — this won't refer to the object. Use shorthand method syntax: { greet() { return this.name; } }.
!
Accidentally mutating an object passed to a function — objects are passed by reference in JavaScript. Always create a copy ({ ...obj }) before modifying.
!
Checking if an object has a property with if (obj.key) — this fails when the value is falsy (0, '', false). Use 'key' in obj or Object.hasOwn(obj, 'key').

Interview Mastery

Dot notation (obj.key) is cleaner and more common — use it when the key is a valid identifier and known at write time. Bracket notation (obj['key'] or obj[variable]) is required when: the key is stored in a variable, the key contains special characters or spaces, or the key is dynamic/computed. Example: const field = 'name'; obj[field] works; obj.field would look for a property literally named 'field'.

The spread operator { ...obj } creates a shallow copy of an object — all top-level key-value pairs are copied into a new object. You can override properties: { ...defaults, theme: 'dark' } — later properties override earlier ones. Limitation: it's shallow. Nested objects are not copied — they're shared by reference. Changing a nested property on the copy also changes the original. For deep copies, use structuredClone() (modern) or JSON.parse(JSON.stringify()) (limited to JSON-safe values).

Several ways: 'key' in obj returns true if the property exists (including inherited). Object.hasOwn(obj, 'key') checks only own properties (modern, preferred). obj.hasOwnProperty('key') — older equivalent. Avoid if (obj.key) — this returns false for falsy values (0, '', false, null) even if the property exists. For optional chaining with fallback: obj?.key ?? defaultValue.

Hands-on Lab Exercises

1
Model a bank account as an object with properties (balance, owner, accountType) and methods (deposit, withdraw, getStatement). Use this correctly in methods.
2

Write a function that merges two settings objects, with the second taking precedence. Handle nested objects correctly.

3
Use Object.entries and Object.fromEntries to write a function that doubles all numeric values in an object.
4

Practice destructuring: extract nested properties from a deeply nested API response object in a single destructuring assignment.

Real-World Practice Scenarios

User profile: Model a user with nested address, create an update function that uses spread to update only changed fields, and a sanitize function that removes sensitive fields using rest destructuring.
Config system: Deep-merge a default config with user overrides — each nested section should be merged, not replaced.
Inventory system: An object where keys are product IDs and values are stock counts. Add, remove, update, and query using Object.keys/values/entries.
API response handling: Destructure a complex API response with nested data, rename fields, set defaults for missing values.

Deepen Your Knowledge