Episode 1 — Fundamentals / 1.22 — Array Methods

1.22.j — Practice Problems

In one sentence: Hands-on exercises that combine map, filter, reduce, and other array methods to solve realistic data-processing tasks — each with a problem statement, hints, solution, and explanation.

Navigation: ← 1.22.i — Functional Thinking · 1.22 Overview →


How to use these problems

  1. Read the problem and try to solve it yourself first.
  2. Check the hints if you get stuck.
  3. Compare your solution with the provided one — there are often multiple valid approaches.
  4. Understand the explanation before moving on.

Problem 1: Filter students by marks

Data:

const students = [
  { name: "Alice", marks: 92 },
  { name: "Bob", marks: 45 },
  { name: "Carol", marks: 78 },
  { name: "Dave", marks: 88 },
  { name: "Eve", marks: 55 },
  { name: "Frank", marks: 96 },
  { name: "Grace", marks: 62 },
];

Tasks:

  1. Get all students who passed (marks >= 60).
  2. Get all students with an A grade (marks >= 90).
  3. Get the names of students who failed (marks < 60).

Hints:

  • Task 1-2: Use filter with a condition on marks.
  • Task 3: Chain filter then map.
Solution
// Task 1: Passed
const passed = students.filter(s => s.marks >= 60);
console.log(passed);
// [Alice(92), Carol(78), Dave(88), Frank(96), Grace(62)]

// Task 2: A grade
const aGrade = students.filter(s => s.marks >= 90);
console.log(aGrade);
// [Alice(92), Frank(96)]

// Task 3: Names of failed students
const failedNames = students
  .filter(s => s.marks < 60)
  .map(s => s.name);
console.log(failedNames);
// ["Bob", "Eve"]

Explanation: filter selects elements by condition. Chaining filter then map is the standard pattern: narrow the set, then transform.


Problem 2: Transform a user list

Data:

const users = [
  { firstName: "Alice", lastName: "Smith", age: 28 },
  { firstName: "Bob", lastName: "Jones", age: 34 },
  { firstName: "Carol", lastName: "White", age: 22 },
  { firstName: "Dave", lastName: "Brown", age: 45 },
];

Tasks:

  1. Extract an array of full names: ["Alice Smith", "Bob Jones", ...]
  2. Create display strings: ["Alice Smith (28)", "Bob Jones (34)", ...]
  3. Create objects with fullName and isAdult properties.

Hints:

  • All tasks use map to transform each object.
  • Task 3: Return an object from the arrow function (use parentheses).
Solution
// Task 1: Full names
const fullNames = users.map(u => `${u.firstName} ${u.lastName}`);
console.log(fullNames);
// ["Alice Smith", "Bob Jones", "Carol White", "Dave Brown"]

// Task 2: Display strings
const display = users.map(u => `${u.firstName} ${u.lastName} (${u.age})`);
console.log(display);
// ["Alice Smith (28)", "Bob Jones (34)", "Carol White (22)", "Dave Brown (45)"]

// Task 3: New objects
const transformed = users.map(u => ({
  fullName: `${u.firstName} ${u.lastName}`,
  isAdult: u.age >= 18,
}));
console.log(transformed);
// [
//   { fullName: "Alice Smith", isAdult: true },
//   { fullName: "Bob Jones", isAdult: true },
//   ...
// ]

Explanation: map is the go-to for transforming each element. Note the ({ }) wrapping for returning objects from arrow functions.


Problem 3: Calculate total price of cart items

Data:

const cart = [
  { name: "Laptop", price: 999.99, qty: 1 },
  { name: "Mouse", price: 29.99, qty: 2 },
  { name: "Keyboard", price: 79.99, qty: 1 },
  { name: "Monitor", price: 349.99, qty: 2 },
  { name: "USB Cable", price: 9.99, qty: 3 },
];

Tasks:

  1. Calculate the total cost of all items (price x qty for each, then sum).
  2. Find the most expensive line item (price x qty).
  3. Get items where the line total exceeds $100, formatted as "Name: $total".

Hints:

  • Task 1: reduce with an accumulator that adds price * qty.
  • Task 2: map to line totals, then Math.max(...arr) or reduce.
  • Task 3: Chain map (add total) → filter (> 100) → map (format string).
Solution
// Task 1: Total cost
const totalCost = cart.reduce((sum, item) => sum + item.price * item.qty, 0);
console.log(totalCost.toFixed(2)); // "1489.92"

// Task 2: Most expensive line item
const lineTotals = cart.map(item => ({
  name: item.name,
  total: item.price * item.qty,
}));
const mostExpensive = lineTotals.reduce(
  (max, item) => item.total > max.total ? item : max,
  lineTotals[0]
);
console.log(mostExpensive);
// { name: "Laptop", total: 999.99 }

// Task 3: Items with line total > $100
const expensiveItems = cart
  .map(item => ({ ...item, total: item.price * item.qty }))
  .filter(item => item.total > 100)
  .map(item => `${item.name}: $${item.total.toFixed(2)}`);
console.log(expensiveItems);
// ["Laptop: $999.99", "Monitor: $699.98"]

Explanation: reduce is the natural choice for summing. For finding the max, reduce with a comparison works well. The chain in Task 3 shows the typical map→filter→map pattern.


Problem 4: Group items by category

Data:

const products = [
  { name: "Laptop", category: "electronics", price: 999 },
  { name: "Shirt", category: "clothing", price: 29 },
  { name: "Phone", category: "electronics", price: 699 },
  { name: "Pants", category: "clothing", price: 49 },
  { name: "Book", category: "books", price: 15 },
  { name: "Tablet", category: "electronics", price: 499 },
  { name: "Hat", category: "clothing", price: 19 },
  { name: "Novel", category: "books", price: 12 },
];

Tasks:

  1. Group products by category into an object: { electronics: [...], clothing: [...], books: [...] }.
  2. Count how many products are in each category: { electronics: 3, clothing: 3, books: 2 }.
  3. Calculate total price per category: { electronics: 2197, clothing: 97, books: 27 }.

Hints:

  • All tasks use reduce with an object as the initial value.
  • Task 1: Push the product into the appropriate category array.
  • Task 2: Increment a counter.
  • Task 3: Add the price to the running total.
Solution
// Task 1: Group by category
const grouped = products.reduce((groups, product) => {
  const key = product.category;
  if (!groups[key]) groups[key] = [];
  groups[key].push(product);
  return groups;
}, {});
console.log(grouped);

// Task 2: Count per category
const counts = products.reduce((acc, product) => {
  acc[product.category] = (acc[product.category] || 0) + 1;
  return acc;
}, {});
console.log(counts);
// { electronics: 3, clothing: 3, books: 2 }

// Task 3: Total price per category
const totals = products.reduce((acc, product) => {
  acc[product.category] = (acc[product.category] || 0) + product.price;
  return acc;
}, {});
console.log(totals);
// { electronics: 2197, clothing: 97, books: 27 }

Explanation: reduce to an object is the standard grouping pattern. The (acc[key] || 0) + value idiom handles the case where the key does not exist yet.


Problem 5: Find the most frequent element

Data:

const votes = ["Alice", "Bob", "Alice", "Carol", "Bob", "Alice", "Bob", "Bob", "Carol", "Alice"];

Tasks:

  1. Count votes for each candidate.
  2. Find the winner (most votes).
  3. Sort candidates by vote count descending.

Hints:

  • Task 1: reduce to a frequency object.
  • Task 2: Use Object.entries() with reduce to find the max.
  • Task 3: Object.entries()sortmap.
Solution
// Task 1: Count votes
const voteCounts = votes.reduce((acc, name) => {
  acc[name] = (acc[name] || 0) + 1;
  return acc;
}, {});
console.log(voteCounts);
// { Alice: 4, Bob: 4, Carol: 2 }

// Task 2: Find winner
const winner = Object.entries(voteCounts).reduce(
  (max, [name, count]) => count > max.count ? { name, count } : max,
  { name: "", count: 0 }
);
console.log(winner);
// { name: "Alice", count: 4 }
// (Alice and Bob are tied — Alice appears first due to iteration order)

// Task 3: Sort by votes descending
const ranked = Object.entries(voteCounts)
  .sort(([, a], [, b]) => b - a)
  .map(([name, count]) => `${name}: ${count} votes`);
console.log(ranked);
// ["Alice: 4 votes", "Bob: 4 votes", "Carol: 2 votes"]

Explanation: The frequency counter pattern (reduce to object) is one of the most important patterns in JavaScript. Object.entries() converts the object to [key, value] pairs that can be sorted.


Problem 6: Chain operations — full pipeline

Data:

const employees = [
  { name: "Alice", department: "Engineering", salary: 95000, active: true },
  { name: "Bob", department: "Marketing", salary: 65000, active: false },
  { name: "Carol", department: "Engineering", salary: 105000, active: true },
  { name: "Dave", department: "Engineering", salary: 88000, active: true },
  { name: "Eve", department: "Marketing", salary: 72000, active: true },
  { name: "Frank", department: "Engineering", salary: 92000, active: true },
  { name: "Grace", department: "HR", salary: 78000, active: true },
];

Tasks:

  1. Get active employees, sorted by salary descending, formatted as "Name (Department) — $salary".
  2. Calculate the average salary of active engineers.
  3. Create a department summary: for each department, list the count and total salary of active employees.

Hints:

  • Task 1: filtersortmap.
  • Task 2: filter (active AND Engineering) → reduce for sum → divide by count.
  • Task 3: filter (active) → reduce to a grouped object.
Solution
// Task 1: Active employees sorted by salary, formatted
const roster = employees
  .filter(e => e.active)
  .sort((a, b) => b.salary - a.salary)
  .map(e => `${e.name} (${e.department}) — $${e.salary.toLocaleString()}`);
console.log(roster);
// [
//   "Carol (Engineering) — $105,000",
//   "Alice (Engineering) — $95,000",
//   "Frank (Engineering) — $92,000",
//   "Dave (Engineering) — $88,000",
//   "Grace (HR) — $78,000",
//   "Eve (Marketing) — $72,000",
// ]

// Task 2: Average salary of active engineers
const activeEngineers = employees.filter(
  e => e.active && e.department === "Engineering"
);
const avgSalary = activeEngineers.reduce((sum, e) => sum + e.salary, 0)
  / activeEngineers.length;
console.log(avgSalary); // 95000

// Task 3: Department summary
const summary = employees
  .filter(e => e.active)
  .reduce((acc, e) => {
    if (!acc[e.department]) {
      acc[e.department] = { count: 0, totalSalary: 0 };
    }
    acc[e.department].count++;
    acc[e.department].totalSalary += e.salary;
    return acc;
  }, {});
console.log(summary);
// {
//   Engineering: { count: 4, totalSalary: 380000 },
//   Marketing: { count: 1, totalSalary: 72000 },
//   HR: { count: 1, totalSalary: 78000 },
// }

Explanation: Task 1 demonstrates the classic filter→sort→map pipeline. Task 2 shows how to compute an average using reduce and length. Task 3 demonstrates a reduce-to-object pattern for aggregation.


Problem 7: Flatten and deduplicate nested arrays

Data:

const tagGroups = [
  ["javascript", "react", "node"],
  ["python", "django", "react"],
  ["javascript", "vue", "node"],
  ["python", "flask"],
  ["react", "next", "javascript"],
];

Tasks:

  1. Flatten into a single array of all tags (with duplicates).
  2. Get unique tags only (no duplicates).
  3. Count how many times each tag appears, sorted by frequency descending.

Hints:

  • Task 1: flat() or flatMap.
  • Task 2: new Set() or filter with indexOf.
  • Task 3: reduce to frequency object → Object.entriessort.
Solution
// Task 1: Flatten
const allTags = tagGroups.flat();
console.log(allTags);
// ["javascript", "react", "node", "python", "django", "react", ...]

// Task 2: Unique tags
const uniqueTags = [...new Set(allTags)];
console.log(uniqueTags);
// ["javascript", "react", "node", "python", "django", "vue", "flask", "next"]

// Task 3: Frequency sorted
const tagFrequency = allTags
  .reduce((acc, tag) => {
    acc[tag] = (acc[tag] || 0) + 1;
    return acc;
  }, {});

const sortedTags = Object.entries(tagFrequency)
  .sort(([, a], [, b]) => b - a)
  .map(([tag, count]) => `${tag}: ${count}`);

console.log(sortedTags);
// ["javascript: 3", "react: 3", "node: 2", "python: 2", "django: 1", "vue: 1", "flask: 1", "next: 1"]

Explanation: flat() handles the flattening. new Set is the most efficient way to deduplicate. The frequency counter + sort pattern combines reduce and sort.


Problem 8: Build a search and filter system

Data:

const products = [
  { id: 1, name: "Wireless Mouse", price: 29.99, category: "electronics", rating: 4.5 },
  { id: 2, name: "USB Keyboard", price: 49.99, category: "electronics", rating: 4.2 },
  { id: 3, name: "Cotton T-Shirt", price: 19.99, category: "clothing", rating: 4.0 },
  { id: 4, name: "Running Shoes", price: 89.99, category: "clothing", rating: 4.8 },
  { id: 5, name: "JavaScript Book", price: 39.99, category: "books", rating: 4.7 },
  { id: 6, name: "Python Book", price: 34.99, category: "books", rating: 4.3 },
  { id: 7, name: "Laptop Stand", price: 59.99, category: "electronics", rating: 4.1 },
  { id: 8, name: "Wool Sweater", price: 69.99, category: "clothing", rating: 3.9 },
];

Tasks:

  1. Write a function searchProducts(query) that filters by name (case-insensitive).
  2. Write a function filterProducts({ category, minPrice, maxPrice, minRating }) that applies all provided filters.
  3. Combine search + filter + sort (by rating descending) into one pipeline.

Hints:

  • Task 1: filter with toLowerCase().includes().
  • Task 2: filter with multiple conditions, checking if each filter is provided.
  • Task 3: Chain all operations together.
Solution
// Task 1: Search by name
function searchProducts(query) {
  const lower = query.toLowerCase();
  return products.filter(p => p.name.toLowerCase().includes(lower));
}
console.log(searchProducts("book"));
// [JavaScript Book, Python Book]

// Task 2: Multi-filter
function filterProducts({ category, minPrice, maxPrice, minRating } = {}) {
  return products.filter(p => {
    if (category && p.category !== category) return false;
    if (minPrice !== undefined && p.price < minPrice) return false;
    if (maxPrice !== undefined && p.price > maxPrice) return false;
    if (minRating !== undefined && p.rating < minRating) return false;
    return true;
  });
}
console.log(filterProducts({ category: "electronics", maxPrice: 50 }));
// [Wireless Mouse, USB Keyboard]

// Task 3: Combined pipeline
function findProducts(query, filters = {}, sortBy = "rating") {
  const lower = query.toLowerCase();
  return products
    .filter(p => p.name.toLowerCase().includes(lower))
    .filter(p => {
      if (filters.category && p.category !== filters.category) return false;
      if (filters.minPrice !== undefined && p.price < filters.minPrice) return false;
      if (filters.maxPrice !== undefined && p.price > filters.maxPrice) return false;
      if (filters.minRating !== undefined && p.rating < filters.minRating) return false;
      return true;
    })
    .sort((a, b) => b[sortBy] - a[sortBy])
    .map(p => `${p.name} — $${p.price} (${p.rating} stars)`);
}

console.log(findProducts("", { minRating: 4.2 }));
// [
//   "Running Shoes — $89.99 (4.8 stars)",
//   "JavaScript Book — $39.99 (4.7 stars)",
//   "Wireless Mouse — $29.99 (4.5 stars)",
//   "Python Book — $34.99 (4.3 stars)",
//   "USB Keyboard — $49.99 (4.2 stars)",
// ]

Explanation: Separate filter calls make each concern clear. The combined pipeline shows a real-world pattern for search + filter + sort in a UI.


Bonus challenge: build map, filter, and reduce from scratch

Try implementing these without using the built-in methods:

Solution
// Custom map
function myMap(arr, callback) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    result.push(callback(arr[i], i, arr));
  }
  return result;
}

// Custom filter
function myFilter(arr, callback) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    if (callback(arr[i], i, arr)) {
      result.push(arr[i]);
    }
  }
  return result;
}

// Custom reduce
function myReduce(arr, callback, initialValue) {
  let accumulator;
  let startIndex;
  if (initialValue !== undefined) {
    accumulator = initialValue;
    startIndex = 0;
  } else {
    if (arr.length === 0) throw new TypeError("Reduce of empty array with no initial value");
    accumulator = arr[0];
    startIndex = 1;
  }
  for (let i = startIndex; i < arr.length; i++) {
    accumulator = callback(accumulator, arr[i], i, arr);
  }
  return accumulator;
}

// Test
console.log(myMap([1, 2, 3], n => n * 2));        // [2, 4, 6]
console.log(myFilter([1, 2, 3, 4], n => n > 2));  // [3, 4]
console.log(myReduce([1, 2, 3], (a, b) => a + b, 0)); // 6

Explanation: Understanding the internals helps you reason about edge cases and performance. Note how reduce handles the initialValue parameter.


Navigation: ← 1.22.i — Functional Thinking · 1.22 Overview →