Lesson 9 · 9 of 20
Higher-order functions and composition · JavaScript
Build JavaScript functions that take and return functions: pipe, compose, logging wrappers and combinable rules for your orders.

A higher-order function takes functions, returns functions, or both. You already know map and filter; here you build your own. pipe takes several functions and returns a new one that passes the value through all of them, left to right, using reduce.
Composition means building a big task out of small, single-purpose steps. cleanUsername chains trim, lower and dots, turning " Ana Maria Ruiz " into ana.maria.ruiz. compose does the same right to left, as in maths: in shout, the text is upper-cased first and then gets its exclamation mark.
withLog is a wrapper (or decorator): it takes a function and returns another that does the same job and also logs its arguments and result. Thanks to ...args it works with any number of parameters. When addVat and coupon are composed, the output shows each step of the calculation before the total.
Functions are data too: every takes a list of rules and returns a single predicate that requires all of them. freeShipping is passed straight to filter, and only order 1 meets both conditions. This style is everywhere in React, one of the DMarlon Studio targets written in JavaScript.
Code · main.js
// pipe and compose take functions and return a new function.const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);// Small single-purpose steps for cleaning a username.const trim = (s) => s.trim();const lower = (s) => s.toLowerCase();const dots = (s) => s.split(" ").join(".");const cleanUsername = pipe(trim, lower, dots);console.log(cleanUsername(" Ana Maria Ruiz "));// compose reads right to left: the last function runs first.const shout = compose((s) => s + "!", (s) => s.toUpperCase());console.log(shout("order shipped"));// A function that wraps another one to add logging.function withLog(name, fn) { return (...args) => { const result = fn(...args); console.log(` ${name}(${args.join(", ")}) = ${result}`); return result; };}const addVat = withLog("addVat", (net) => +(net * 1.21).toFixed(2));const coupon = withLog("coupon", (price) => price - 5);console.log("Checkout:", pipe(addVat, coupon)(100));// Functions as data: a list of rules combined into one test.const every = (...rules) => (item) => rules.every((rule) => rule(item));const freeShipping = every((o) => o.total > 50, (o) => o.country === "ES");const orders = [{ id: 1, total: 80, country: "ES" }, { id: 2, total: 30, country: "ES" }, { id: 3, total: 90, country: "FR" }];console.log("Free shipping:", orders.filter(freeShipping).map((o) => `#${o.id}`).join(", "));Output · node main.js
ana.maria.ruiz ORDER SHIPPED! addVat(100) = 121 coupon(121) = 116 Checkout: 116 Free shipping: #1
Key points
- A higher-order function takes or returns functions.
pipeapplies functions left to right;compose, right to left.- A wrapper adds behaviour without changing the original function.
- Rules written as functions can be combined like data.
Exercise
Add another rule to freeShipping (say, no more than 10 items) and use pipe to build a formatPrice function that rounds to two decimals and appends EUR.