Episode 1 — Fundamentals / 1.23 — Objects

1.23.g — Practice Problems

In one sentence: Eight hands-on coding problems that reinforce every concept from 1.23.a1.23.f — from building user profiles to comparing objects and inverting key-value pairs.

Navigation: ← 1.23 Overview · 1.23-Exercise-Questions →


How to use this material

  1. Read the problem statement and try to solve it yourself before looking at hints.
  2. Write actual code — do not just read the solution.
  3. Test edge cases — empty objects, missing properties, different types.
  4. Compare your solution with the provided one — there are often multiple valid approaches.

Problem 1: User profile system (CRUD)

Problem statement

Create a userProfile object with properties name, email, age, and bio. Then write four operations:

  1. Create — build the initial object.
  2. Read — write a function getField(user, fieldName) that safely returns the value or "Field not found".
  3. Update — write a function updateField(user, fieldName, newValue) that updates a field only if it already exists.
  4. Delete — write a function removeField(user, fieldName) that removes a field and returns a new object (immutable).

Hints

  • Use bracket notation for dynamic field access.
  • Use Object.hasOwn to check existence.
  • Use destructuring + rest for immutable delete.

Solution

// CREATE
const userProfile = {
  name: "Alice",
  email: "alice@example.com",
  age: 25,
  bio: "Full-stack developer",
};

// READ
function getField(user, fieldName) {
  if (Object.hasOwn(user, fieldName)) {
    return user[fieldName];
  }
  return "Field not found";
}

console.log(getField(userProfile, "name"));    // "Alice"
console.log(getField(userProfile, "phone"));   // "Field not found"

// UPDATE (mutable)
function updateField(user, fieldName, newValue) {
  if (Object.hasOwn(user, fieldName)) {
    user[fieldName] = newValue;
    return true;
  }
  return false; // field doesn't exist — no update
}

updateField(userProfile, "age", 26);
console.log(userProfile.age); // 26

updateField(userProfile, "phone", "555-0123"); // returns false — no "phone" field
console.log(userProfile.phone); // undefined — was not added

// DELETE (immutable)
function removeField(user, fieldName) {
  const { [fieldName]: removed, ...rest } = user;
  return rest;
}

const withoutBio = removeField(userProfile, "bio");
console.log(withoutBio);     // { name: "Alice", email: "alice@example.com", age: 26 }
console.log(userProfile.bio); // "Full-stack developer" — original unchanged

Explanation

  • Dynamic destructuring { [fieldName]: removed, ...rest } extracts the named field into removed and collects everything else into rest.
  • The original object is never mutated by removeField because spread creates a new object.

Problem 2: Shopping cart object

Problem statement

Create a shopping cart object with:

  • An items array (each item has name, price, quantity)
  • An addItem(name, price, quantity) method
  • A removeItem(name) method
  • A getTotal() method that calculates the total price
  • A getSummary() method that returns a formatted string

Hints

  • Use findIndex to locate items by name.
  • getTotal should multiply price by quantity for each item, then sum.

Solution

const cart = {
  items: [],

  addItem(name, price, quantity = 1) {
    const existing = this.items.find(item => item.name === name);
    if (existing) {
      existing.quantity += quantity;
    } else {
      this.items.push({ name, price, quantity });
    }
  },

  removeItem(name) {
    const index = this.items.findIndex(item => item.name === name);
    if (index !== -1) {
      this.items.splice(index, 1);
      return true;
    }
    return false;
  },

  getTotal() {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  },

  getSummary() {
    if (this.items.length === 0) return "Cart is empty.";

    const lines = this.items.map(
      item => `  ${item.name} x${item.quantity} @ $${item.price.toFixed(2)} = $${(item.price * item.quantity).toFixed(2)}`
    );
    lines.push(`  TOTAL: $${this.getTotal().toFixed(2)}`);
    return lines.join("\n");
  },
};

// Test
cart.addItem("Widget", 9.99, 2);
cart.addItem("Gadget", 24.99, 1);
cart.addItem("Widget", 9.99, 3); // adds to existing quantity

console.log(cart.getSummary());
//   Widget x5 @ $9.99 = $49.95
//   Gadget x1 @ $24.99 = $24.99
//   TOTAL: $74.94

cart.removeItem("Gadget");
console.log(cart.getTotal()); // 49.95

Explanation

  • addItem checks for an existing item by name to avoid duplicates — it increments quantity instead.
  • getTotal uses reduce to accumulate price * quantity across all items.
  • this refers to the cart object because the methods are called with dot notation on cart.

Problem 3: Student record with nested grades

Problem statement

Given a student object with nested grades (an object where keys are subjects and values are arrays of scores), write:

  1. getSubjectAverage(student, subject) — average for one subject.
  2. getOverallAverage(student) — average across all subjects.
  3. getHighestSubject(student) — the subject with the highest average.

Hints

  • Use Object.entries to iterate over subjects.
  • Sum an array with reduce.

Solution

const student = {
  name: "Alice",
  id: "STU-001",
  grades: {
    math: [95, 88, 92, 78],
    english: [85, 90, 88, 92],
    science: [72, 68, 80, 75],
    history: [91, 87, 93, 89],
  },
};

function getSubjectAverage(student, subject) {
  const scores = student.grades?.[subject];
  if (!scores || scores.length === 0) return null;
  const sum = scores.reduce((total, score) => total + score, 0);
  return sum / scores.length;
}

function getOverallAverage(student) {
  const allScores = Object.values(student.grades).flat();
  if (allScores.length === 0) return null;
  const sum = allScores.reduce((total, score) => total + score, 0);
  return sum / allScores.length;
}

function getHighestSubject(student) {
  let highest = { subject: null, average: -Infinity };

  for (const [subject, scores] of Object.entries(student.grades)) {
    const avg = getSubjectAverage(student, subject);
    if (avg > highest.average) {
      highest = { subject, average: avg };
    }
  }

  return highest;
}

// Test
console.log(getSubjectAverage(student, "math"));    // 88.25
console.log(getSubjectAverage(student, "science"));  // 73.75
console.log(getOverallAverage(student));              // 83.4375
console.log(getHighestSubject(student));
// { subject: "history", average: 90 }

Explanation

  • Object.values(student.grades).flat() collects all scores into a single flat array.
  • getHighestSubject iterates entries and tracks the subject with the maximum average.

Problem 4: Merge two objects (shallow)

Problem statement

Write a function mergeObjects(obj1, obj2) that:

  • Creates a new object (does not mutate either input).
  • Properties from obj2 override obj1 if keys overlap.
  • Returns the merged result.

Test with overlapping and non-overlapping keys.

Hints

  • Use the spread operator.

Solution

function mergeObjects(obj1, obj2) {
  return { ...obj1, ...obj2 };
}

// Test
const defaults = { theme: "light", fontSize: 14, language: "en", showTips: true };
const userPrefs = { theme: "dark", fontSize: 18 };

const merged = mergeObjects(defaults, userPrefs);
console.log(merged);
// { theme: "dark", fontSize: 18, language: "en", showTips: true }

// Originals unchanged
console.log(defaults.theme);   // "light"
console.log(userPrefs.language); // undefined

Explanation

  • The spread operator copies properties left to right; later properties overwrite earlier ones with the same key.
  • Neither input is mutated because { ... } creates a new object.

Problem 5: Count properties in an object

Problem statement

Write a function countProperties(obj) that returns:

  • total — number of own properties.
  • byType — an object counting how many values of each type exist.

Hints

  • Use Object.values and typeof.

Solution

function countProperties(obj) {
  const values = Object.values(obj);
  const byType = {};

  for (const value of values) {
    const type = typeof value;
    byType[type] = (byType[type] ?? 0) + 1;
  }

  return {
    total: values.length,
    byType,
  };
}

// Test
const data = {
  name: "Alice",
  age: 25,
  isAdmin: true,
  bio: "Developer",
  score: 98.5,
  greet() { return "hi"; },
  address: { city: "Portland" },
  tags: ["js", "css"],
  nothing: null,
};

console.log(countProperties(data));
// {
//   total: 9,
//   byType: { string: 2, number: 2, boolean: 1, function: 1, object: 3 }
// }
// Note: null, array, and plain object all have typeof "object"

Explanation

  • typeof null is "object", typeof [] is "object" — both counted under "object".
  • The ?? 0 pattern initializes the count to 0 if the type key does not exist yet.

Problem 6: Invert keys and values

Problem statement

Write a function invertObject(obj) that swaps keys and values. If a value is not a string or number, skip it. If duplicate values exist, the last key wins.

Hints

  • Use Object.entries and filter by type.

Solution

function invertObject(obj) {
  const result = {};

  for (const [key, value] of Object.entries(obj)) {
    if (typeof value === "string" || typeof value === "number") {
      result[value] = key;
    }
  }

  return result;
}

// Test
const original = { a: 1, b: 2, c: 3, d: "hello", e: true, f: 2 };

console.log(invertObject(original));
// { 1: "a", 2: "f", 3: "c", hello: "d" }
// Note: key "2" is "f" (last wins, since both b and f had value 2)
// Boolean `true` was skipped

Explanation

  • Only string and number values become valid keys in the inverted object.
  • When multiple keys share the same value, the last one processed overwrites earlier ones.

Problem 7: Compare two objects (shallow equality)

Problem statement

Write a function shallowEqual(obj1, obj2) that returns true if both objects have the same keys with the same values (using === for value comparison).

Hints

  • Compare key counts first (early return).
  • Then check every key-value pair.

Solution

function shallowEqual(obj1, obj2) {
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);

  // Different number of keys — not equal
  if (keys1.length !== keys2.length) return false;

  // Check every key in obj1 exists in obj2 with the same value
  for (const key of keys1) {
    if (!Object.hasOwn(obj2, key) || obj1[key] !== obj2[key]) {
      return false;
    }
  }

  return true;
}

// Test
console.log(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 }));   // true
console.log(shallowEqual({ a: 1, b: 2 }, { b: 2, a: 1 }));   // true (order doesn't matter)
console.log(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 3 }));   // false (different value)
console.log(shallowEqual({ a: 1, b: 2 }, { a: 1 }));          // false (different keys)
console.log(shallowEqual({}, {}));                              // true

// Limitation — nested objects:
const addr = { city: "Portland" };
console.log(shallowEqual({ a: addr }, { a: addr }));           // true  (same reference)
console.log(shallowEqual({ a: { x: 1 } }, { a: { x: 1 } })); // false (different references!)

Explanation

  • Shallow equality only uses === on values — nested objects must be the same reference to be considered equal.
  • The key count check is an optimization: if counts differ, we can immediately return false.
  • We check Object.hasOwn(obj2, key) to ensure obj2 actually has the key (not just an undefined value from a missing key).

Problem 8: Find all keys with a specific value type

Problem statement

Write a function keysByType(obj, targetType) that returns an array of keys whose values match the given typeof string.

Also write keysWithArrayValues(obj) that finds keys where the value is an array (since typeof [] is "object").

Hints

  • Use Array.isArray for array detection.

Solution

function keysByType(obj, targetType) {
  return Object.entries(obj)
    .filter(([, value]) => typeof value === targetType)
    .map(([key]) => key);
}

function keysWithArrayValues(obj) {
  return Object.entries(obj)
    .filter(([, value]) => Array.isArray(value))
    .map(([key]) => key);
}

// Test
const data = {
  name: "Alice",
  age: 25,
  isAdmin: true,
  scores: [95, 88, 72],
  address: { city: "Portland" },
  tags: ["js", "css"],
  greet() { return "hi"; },
  rating: 4.5,
};

console.log(keysByType(data, "string"));   // ["name"]
console.log(keysByType(data, "number"));   // ["age", "rating"]
console.log(keysByType(data, "boolean"));  // ["isAdmin"]
console.log(keysByType(data, "function")); // ["greet"]
console.log(keysByType(data, "object"));   // ["scores", "address", "tags"] — includes arrays!

console.log(keysWithArrayValues(data));    // ["scores", "tags"] — only actual arrays

Explanation

  • typeof cannot distinguish arrays from objects (both return "object"), so Array.isArray is needed for precise array detection.
  • The filter + map pipeline on entries is a clean, declarative pattern.

Summary

ProblemCore concepts practiced
1. User profile CRUDDynamic access, Object.hasOwn, destructuring + rest
2. Shopping cartMethods, this, reduce, array inside object
3. Nested gradesObject.entries, Object.values, .flat(), nested access
4. Merge objectsSpread operator, immutability
5. Count propertiesObject.values, typeof, dynamic key counting
6. Invert keys/valuesObject.entries, type filtering, computed keys
7. Shallow equalityObject.keys, reference vs value comparison
8. Keys by typeObject.entries, typeof, Array.isArray

Navigation: ← 1.23 Overview · 1.23-Exercise-Questions →