map, filter, reduce on a Real List of Orders
Five questions a manager would ask about a list of orders, each answered with map, filter, reduce or a plain loop. Real data includes an empty order, and the code has to survive it.
Here's a list of orders and five questions a manager would actually ask about it. Each answer is a few lines of map, filter or reduce. Work through them in order. By the end you'll know which method fits which question, and where a plain loop is the better answer.
Skip ahead: If you only need the syntax, the quick reference is at the end.
The data
Every example starts with this list. Paste it at the top of a file called orders.js and add each snippet below it. Run it with node orders.js.
const orders = [
{ id: 101, customer: "Ana", status: "paid", items: [{ sku: "COF", price: 180, qty: 2 }, { sku: "BRD", price: 65, qty: 1 }] },
{ id: 102, customer: "Ben", status: "pending", items: [{ sku: "COF", price: 180, qty: 1 }] },
{ id: 103, customer: "Ana", status: "paid", items: [{ sku: "TEA", price: 120, qty: 3 }] },
{ id: 104, customer: "Cris", status: "cancelled", items: [{ sku: "BRD", price: 65, qty: 4 }] },
{ id: 105, customer: "Dina", status: "paid", items: [] },
];
Notice order 105: paid, but no items. Real data has records like that. Your code should survive it.
Question 1: What's each order's total?
One number for each order, so map. Inside, the total of the items is a reduce:
const orderTotal = order => order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
const withTotals = orders.map(order => ({ id: order.id, customer: order.customer, total: orderTotal(order) }));
console.log(withTotals);
[
{ id: 101, customer: 'Ana', total: 425 },
{ id: 102, customer: 'Ben', total: 180 },
{ id: 103, customer: 'Ana', total: 360 },
{ id: 104, customer: 'Cris', total: 260 },
{ id: 105, customer: 'Dina', total: 0 }
]
The 0 at the end of reduce is why order 105 gives 0 instead of crashing. Also note the brackets in order => ({ ... }). Without them, the braces are read as a function body and you get undefined.
Question 2: How much did we actually collect?
Only paid orders, then one number: filter, then reduce.
const orderTotal = order => order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
const collected = orders
.filter(order => order.status === "paid")
.reduce((sum, order) => sum + orderTotal(order), 0);
console.log(collected);
785
Question 3: Which paid orders are empty?
Probably a bug somewhere upstream, so someone should look. Two conditions in one filter, then map to just the ids:
const suspicious = orders
.filter(order => order.status === "paid" && order.items.length === 0)
.map(order => order.id);
console.log(suspicious);
[ 105 ]
Question 4: How many of each product did we sell (paid only)?
Now the result is an object keyed by SKU, and each order has several items. You can do it with reduce, but a loop reads better:
const unitsBySku = {};
for (const order of orders.filter(o => o.status === "paid")) {
for (const item of order.items) {
unitsBySku[item.sku] = (unitsBySku[item.sku] ?? 0) + item.qty;
}
}
console.log(unitsBySku);
{ COF: 2, BRD: 1, TEA: 3 }
filter for the "paid only" part, a loop for the counting. Mixing them is fine. Use whichever makes each step obvious.
💡 Tip: If your Node version is 21 or later, Object.groupBy(orders, o => o.customer) groups a list into an object of arrays in one call. It's handy for "orders per customer". Check your target browsers support it before using it in front-end code.
Question 5: Who are our paying customers, top spender first?
const orderTotal = order => order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
const spent = {};
for (const order of orders) {
if (order.status !== "paid") continue;
spent[order.customer] = (spent[order.customer] ?? 0) + orderTotal(order);
}
const ranking = Object.entries(spent)
.toSorted(([, a], [, b]) => b - a)
.map(([customer, total]) => `${customer}: ${total}`);
console.log(ranking);
[ 'Ana: 785', 'Dina: 0' ]
Object.entries turns the object into [name, total] pairs so you can sort and map them. toSorted returns a sorted copy. The older sort would work here too, but it sorts in place, which surprises people when the array is used elsewhere.
âš Warning: Don't change the original objects inside map, like orders.map(o => { o.total = orderTotal(o); return o; }). You get a "new" array full of the same, now modified, objects. Any other code holding orders sees the change. Return new objects instead, like Question 1 does.
Quick reference
- One output per input:
map - Keep some:
filter - One value from many:
reduce, with a starting value - First match:
find - Any or all:
some,every - Building an object, nested lists, or stopping early: a
for...ofloop
Before you write the next report, say the question out loud. Does it start with "for each", "which ones" or "how much in total"?
About
Answer five report questions on one list of orders with map, filter, reduce and a for...of loop, including an order with no items. After this, you can pick the right method from how the question is worded.
- Responsible
- Jay P
- Last Update
- 09/06/2026
- Reading Time
- 4 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.