Functions as Values: map, filter, reduce
Callbacks, higher-order functions, and the functional toolkit โ plus when a plain for-loop is the better choice.
In Beginner you used map and filter. Here we treat functions as
first-class values โ the idea that makes those methods possible โ and build
our own.
Functions are values
A function can live in a variable, be passed to another function, and be returned from one. A function that takes or returns functions is a higher-order function (HOF):
// takes a function โ HOF
function repeat(times, action) {
for (let i = 0; i < times; i++) action(i);
}
repeat(3, (i) => console.log("run " + i));
// returns a function โ HOF
const multiply = (a) => (b) => a * b;
const triple = multiply(3);
triple(4); // 12
map / filter / reduce, precisely
const orders = [
{ id: 1, user: "ada", total: 120, status: "paid" },
{ id: 2, user: "linh", total: 35, status: "open" },
{ id: 3, user: "ada", total: 80, status: "paid" },
];
// map: same length, transformed items
const totals = orders.map((o) => o.total); // [120, 35, 80]
// filter: subset, same shape
const paid = orders.filter((o) => o.status === "paid");
// reduce: any shape โ any other shape
const byUser = orders.reduce((acc, o) => {
acc[o.user] = (acc[o.user] ?? 0) + o.total;
return acc;
}, {}); // { ada: 200, linh: 35 }
reduce deserves respect: it is the general-purpose one. map and
filter can both be written with it, and so can grouping, counting, and
flattening. When a data question sounds like "combine everything into โฆ",
reduce is the answer.
Chaining and its limits
const topAda = orders
.filter((o) => o.user === "ada")
.map((o) => o.total)
.reduce((sum, t) => sum + t, 0); // 200
Chains read top-to-bottom like a pipeline. Two cautions:
- Each step copies the array. On hot paths over huge arrays, one
forloop beats three passes. - A chain longer than ~5 steps is harder to read than two named steps. Refactoring into small named functions is the intermediate move:
const paidFor = (user) => orders.filter((o) => o.user === user && o.status === "paid");
const sumTotals = (os) => os.reduce((s, o) => s + o.total, 0);
sumTotals(paidFor("ada")); // 200
Why this matters at Intermediate
Data transformation is most of what applications do. The HOF toolkit โ and knowing when not to use it โ is the difference between copy-pasting loops and expressing intent.