Skip to content

Expert · Functional patterns and testing · topic 48 of 61 · 25 min

Composition, currying and pipelines

Explanation

Composition builds a new function by feeding the output of one function into the next. pipe(f, g, h)(x) means h(g(f(x))), which reads like a list of steps. Currying turns a function of several arguments into a chain of one-argument functions, and partial application fixes some arguments in advance.

Small functions, combined

Instead of one big function that cleans, validates, converts and formats a value, write four tiny functions and connect them. Each is easy to test and reuse. Composition is the act of connecting them so that one function's output becomes the next function's input.

javascript
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const collapseSpaces = (s) => s.replace(/\s+/g, ' ');

const cleanName = (s) => collapseSpaces(lower(trim(s)));  // read inside-out: hard
console.log(cleanName('  ASHA   RAO ')); // 'asha rao'

pipe and compose

pipe takes functions and returns a new function that runs them left to right. compose does the same right to left (the mathematical order). pipe usually reads more naturally.

javascript
const pipe = (...fns) => (input) => fns.reduce((value, fn) => fn(value), input);
const compose = (...fns) => (input) => fns.reduceRight((value, fn) => fn(value), input);

const cleanName2 = pipe(trim, lower, collapseSpaces);
console.log(cleanName2('  ASHA   RAO ')); // 'asha rao'

Currying

A curried function takes its arguments one at a time, returning a new function after each, until it has them all. It is named after the mathematician Haskell Curry. Currying makes it easy to create specialised versions of general functions.

javascript
const addGst = (rate) => (amount) => Math.round(amount * (1 + rate / 100));
const gst5 = addGst(5);
const gst18 = addGst(18);
console.log(gst5(200), gst18(200)); // 210 236
console.log([100, 250].map(gst18)); // [118, 295]

Partial application

Partial application fixes some arguments of a normal function and returns a function waiting for the rest. bind can do it, or a small helper.

javascript
function discount(percent, max, price) {
  return price - Math.min(price * percent / 100, max);
}
const festival = discount.bind(null, 20, 500);   // percent 20, max 500 fixed
const partial = (fn, ...fixed) => (...rest) => fn(...fixed, ...rest);
const member = partial(discount, 5, 200);
console.log(festival(1000), festival(5000), member(1000)); // 800 4500 950

Putting it together: a pricing pipeline

javascript
const applyCoupon = (pct) => (p) => p * (1 - pct / 100);
const addShipping = (fee, freeAbove) => (p) => (p >= freeAbove ? p : p + fee);
const roundRupee = (p) => Math.round(p);
const finalPrice = pipe(applyCoupon(10), addShipping(40, 499), roundRupee);
console.log(finalPrice(450), finalPrice(600)); // 445 540
Careful: Do not turn every function into tiny curried pieces. Point-free code (functions that never name their arguments) can become a puzzle for teammates. Use composition where it makes a sequence of steps clearer, and plain code everywhere else.
Think of it like this: A pipe is like an assembly line in a biscuit factory: dough goes in, and each machine does one job (cut, bake, pack) before passing it on. You can add, remove or reorder machines without rebuilding the whole factory.
Tip: Functions in a pipe should take ONE argument and return one value. If a step needs extra settings, curry it: applyCoupon(10) returns the one-argument step.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Composition, currying and pipelines · 1/6

Composition

In this lesson you will combine small functions into bigger ones.

Prefer a video by a teacher? Find free videos on “javascript function composition pipe currying partial application” on YouTube ↗

In real life: Cleaning customer records before import

A CRM import cleans thousands of phone numbers from Excel sheets: remove spaces and dashes, strip a leading +91 or 0, and check the length. Each rule is a tiny function, and the import runs every number through a pipe of them.

javascript
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
const digitsOnly = (s) => s.replace(/[^\d+]/g, '');
const stripPrefix = (s) => s.replace(/^(\+91|0)/, '');
const valid = (s) => (/^[6-9]\d{9}$/.test(s) ? s : null);
const cleanPhone = pipe(digitsOnly, stripPrefix, valid);
console.log(['+91 98765-43210', '098765 43210', '12345'].map(cleanPhone));
// ['9876543210', '9876543210', null]

Practice (0/3 done)

Task 1 · Code · Javascript

Write pipe(...fns) that returns a function running fns from left to right on its input.

Task 2 · Code · Javascript

Write a curried function between(min) that returns (max) => (n) => true when min <= n <= max. Then use it to build isValidAge = between(18)(60).

Task 3 · Code · Javascript

Build a pricing function finalPrice with pipe from three curried steps: applyCoupon(pct) (subtract pct percent), addShipping(fee, freeAbove) (add fee unless the price is already >= freeAbove), and roundRupee. finalPrice = pipe(applyCoupon(10), addShipping(40, 499), roundRupee).

Quick quiz

Question 1

1.pipe(f, g)(x) equals…

Question 2

2.const add = (a) => (b) => a + b; What is add(2)(3)?

Question 3

3.What does partial application do?

Question 4

4.Why should each step in a pipe take one argument?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

pipe(f, g)(x) equals…

Answer: g(f(x))

pipe runs left to right: f first, then g.

const add = (a) => (b) => a + b; What is add(2)(3)?

Answer: 5

add(2) returns b => 2 + b; calling it with 3 gives 5.

What does partial application do?

Answer: Fixes some arguments and returns a function for the rest

It pre-fills some arguments.

Why should each step in a pipe take one argument?

Answer: Each step receives only the previous step's single return value

A function returns one value, which becomes the next input.

In a curry helper, what does fn.length give?Related

Answer: The number of declared parameters

Function.length is the count of declared parameters (before any default or rest).

From Classic coding questions

In "sort names.txt | uniq -c", what does the pipe do?Related

Answer: Sends sort's output to uniq -c as input

A pipe connects stdout of the left command to stdin of the right one.

From Reading text: grep, less, pipes and redirection

Which relationship suits composition best?Related

Answer: A Car has an Engine

"Has-a" relationships are modelled by holding the part inside.

From Inheritance and composition

Which pipeline lists the 5 most frequent client IPs, assuming the IP is the first column of access.log?Related

Answer: awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -n 5

awk picks the IP, sort groups equal lines, uniq -c counts them, sort -rn puts the biggest first.

From Bash scripting, text tools and cron

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Functional patterns and testing” before its exam: Memoization, debounce and throttle, Testing your code, Test doubles and testable design.
  2. Module 10 exam: Functional patterns and testing · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Performance and Node.js. 61 topics left in the course before the final exam.

← Previous: Pure functions and immutability