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
- Read the problem and try to solve it yourself first.
- Check the hints if you get stuck.
- Compare your solution with the provided one — there are often multiple valid approaches.
- 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:
- Get all students who passed (marks >= 60).
- Get all students with an A grade (marks >= 90).
- Get the names of students who failed (marks < 60).
Hints:
- Task 1-2: Use
filterwith a condition onmarks. - Task 3: Chain
filterthenmap.
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:
- Extract an array of full names:
["Alice Smith", "Bob Jones", ...] - Create display strings:
["Alice Smith (28)", "Bob Jones (34)", ...] - Create objects with
fullNameandisAdultproperties.
Hints:
- All tasks use
mapto 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:
- Calculate the total cost of all items (price x qty for each, then sum).
- Find the most expensive line item (price x qty).
- Get items where the line total exceeds $100, formatted as
"Name: $total".
Hints:
- Task 1:
reducewith an accumulator that addsprice * qty. - Task 2:
mapto line totals, thenMath.max(...arr)orreduce. - 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:
- Group products by category into an object:
{ electronics: [...], clothing: [...], books: [...] }. - Count how many products are in each category:
{ electronics: 3, clothing: 3, books: 2 }. - Calculate total price per category:
{ electronics: 2197, clothing: 97, books: 27 }.
Hints:
- All tasks use
reducewith 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:
- Count votes for each candidate.
- Find the winner (most votes).
- Sort candidates by vote count descending.
Hints:
- Task 1:
reduceto a frequency object. - Task 2: Use
Object.entries()withreduceto find the max. - Task 3:
Object.entries()→sort→map.
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:
- Get active employees, sorted by salary descending, formatted as
"Name (Department) — $salary". - Calculate the average salary of active engineers.
- Create a department summary: for each department, list the count and total salary of active employees.
Hints:
- Task 1:
filter→sort→map. - Task 2:
filter(active AND Engineering) →reducefor sum → divide by count. - Task 3:
filter(active) →reduceto 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:
- Flatten into a single array of all tags (with duplicates).
- Get unique tags only (no duplicates).
- Count how many times each tag appears, sorted by frequency descending.
Hints:
- Task 1:
flat()orflatMap. - Task 2:
new Set()orfilterwithindexOf. - Task 3:
reduceto frequency object →Object.entries→sort.
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:
- Write a function
searchProducts(query)that filters by name (case-insensitive). - Write a function
filterProducts({ category, minPrice, maxPrice, minRating })that applies all provided filters. - Combine search + filter + sort (by rating descending) into one pipeline.
Hints:
- Task 1:
filterwithtoLowerCase().includes(). - Task 2:
filterwith 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 →