Introduction
In our previous lesson, we saw how chaining higher-order functions like map, filter, and reduce creates powerful, declarative data-processing pipelines. You might have found yourself writing code like data.filter(...).map(...). While effective, this pattern is tied to the array prototype. What if we could create a similar pipeline using any set of compatible functions, independent of a specific data type?
This is where function composition comes in. This lesson formalizes the pipeline concept you've already seen, allowing you to build new functions by plumbing together smaller, reusable ones. Your goal is to implement function composition to create a reusable pipeline of operations. As a software engineer, you'll recognize this as a powerful pattern for abstraction and building modular, testable code, much like the Unix philosophy of small, single-purpose tools that can be piped together.
To get started, consider the following image, which provides a great mental model for function composition.

What is Function Composition?
At its core, function composition is the process of creating a new function by combining two or more existing functions. The output of one function becomes the input of the next in the sequence. For example, if we have a function f(x) and a function g(x), their composition would look like g(f(x)). We first apply f to our data, then take that result and apply g to it.
This approach encourages you to build a library of small, pure, and highly focused functions. Each function has a single responsibility, making it easy to understand, test, and debug. You then assemble these simple "building blocks" to perform complex tasks.
The following article by James Sinclair offers an excellent introduction to this idea. It starts with a simple two-function composition and builds from there.
JavaScript function composition: What's the big deal? - James Sinclair
This article provides a clear, step-by-step introduction to the concept and utility of function composition.
Read the section titled What is function composition?. Pay attention to how manually nesting function calls, like linkify(imagify(emphasize(str))), quickly becomes difficult to read. This is the problem that composition helper functions aim to solve.
To further ground this in principle, it's helpful to understand the philosophy behind this style of programming.
Functional programming for your everyday javascript
This article explores the core philosophy behind function composition and its practical application.
Please read the first three sections: What you need to know: This covers the Unix philosophy of single-responsibility functions and the concept of functions as "first-class citizens" in JavaScript. Natural composition: This reinforces the "parenthesis problem" of manual composition.
Automatic Composition: compose and pipe
Manually nesting functions is not scalable. To solve this, we can create higher-order functions that automate the process of creating a pipeline. The two most common patterns for this are compose and pipe. They do the same thing but differ in the order of operations.
1. The compose Function (Right-to-Left)
By convention in functional programming (stemming from mathematics), compose executes functions from right to left. This mimics the "inside-out" evaluation of nested function calls.
compose(h, g, f) is equivalent to (...args) => h(g(f(...args))).
The last function in the list (f) is the first to receive the data. This can feel a bit counterintuitive when reading code from top to bottom or left to right.
We can implement a generic compose function using the reduceRight array method. It takes a list of functions, and for a given initial value, it "reduces" them by applying each function to the accumulated result, starting from the rightmost function.
const compose = (...fns) => initialVal => fns.reduceRight((acc, fn) => fn(acc), initialVal);
// --- Example Usage ---
const add5 = num => num + 5;
const multiplyBy2 = num => num * 2;
const subtract10 = num => num - 10;
// The composed function will execute in this order:
// 1. add5(10) -> 15
// 2. multiplyBy2(15) -> 30
// 3. subtract10(30) -> 20
const calculate = compose(subtract10, multiplyBy2, add5);
const result = calculate(10); // result is 20
console.log(result);
The video below offers a great walkthrough of building and using a compose function.
Pipe Functions and Compose Functions | Javascript Functional Programming Tutorial
This tutorial from Dave Gray explains how compose works and shows you how to build it from scratch.
Watch from the start of the explanation. The video first illustrates the right-to-left execution order and then implements a compose function using reduceRight, just as we did above.
2. The pipe (or flow) Function (Left-to-Right)
Many developers find the right-to-left execution of compose unnatural to read. The pipe function (sometimes called flow) was created as a more intuitive alternative. It executes functions from left to right.
pipe(f, g, h) is equivalent to (...args) => h(g(f(...args))).
The first function in the list (f) is the first to receive the data. This reads more like a sequence of steps.
Implementing pipe is nearly identical to compose, but we use the standard reduce method instead of reduceRight.
const pipe = (...fns) => initialVal => fns.reduce((acc, fn) => fn(acc), initialVal);
// --- Example Usage ---
// The composed function will now execute in this order:
// 1. add5(10) -> 15
// 2. multiplyBy2(15) -> 30
// 3. subtract10(30) -> 20
const calculatePipe = pipe(add5, multiplyBy2, subtract10);
const resultPipe = calculatePipe(10); // result is 20
console.log(resultPipe);
Notice the list of functions is now in the order of execution, which is often easier to reason about.

The Power of Pipelines
Now that we have these helper functions, we can return to the text processing example from the Sinclair article and see how much cleaner it becomes.
JavaScript function composition: What's the big deal? - James Sinclair
Let's see how these helper functions create elegant and powerful data pipelines.
Read the following sections: Compose: Shows how compose cleans up the nested calls. Flow: Introduces a left-to-right version, which we've called pipe. Pipe: This section introduces a slightly different pipe that immediately executes, showing how composition can replace method chaining (.filter().map()). What’s the big deal?: This is the key takeaway. It explains how composition shifts your thinking towards declarative expressions and allows you to swap out implementations without changing the high-level logic.
The final point from the article is crucial: by defining a process as a composition of abstract steps (filter, map, join), you separate the intent of your pipeline from the implementation of its parts. You could replace the array-based helpers with versions that use generators for lazy evaluation, and the pipeline itself would not need to change. This is a profound concept in software design that leads to more flexible and maintainable systems.
A Note on Function Signatures
So far, our examples have conveniently used unary functions—functions that accept exactly one argument. This makes them easy to plug together. But what happens when a function in your pipeline needs more than one argument, like split(delimiter, string) or replace(pattern, replacement, string)?
You can handle this by creating an anonymous arrow function inside the pipeline, but this can get clumsy. A more elegant solution involves two other functional programming techniques: currying and partial application. These techniques allow you to transform a function that takes multiple arguments into a sequence of functions that each take a single argument, making them perfectly suited for composition.
Pipe Functions and Compose Functions | Javascript Functional Programming Tutorial
Let's get a brief preview of how to handle functions with more than one parameter.
Watch this segment on handling multiple parameters. The video shows two approaches: using an inline anonymous function and creating a specialized unary function via currying. Don't worry about mastering currying just yet; we'll dedicate our entire next lesson to it.
Conclusion
In this lesson, you've moved beyond simple method chaining to a more generalized and powerful pattern for creating data-processing pipelines. By implementing compose and pipe, you've created reusable tools that are at the heart of the functional programming style in JavaScript.
Key Takeaways:
- Function composition is the act of creating a new function by combining smaller, single-purpose functions in a sequence.
- The
composefunction creates a pipeline that executes from right to left, mimicking nested function calls. - The
pipe(orflow) function creates a pipeline that executes from left to right, which is often more intuitive to read. - Both are typically implemented using
reduceRightandreduce, respectively, to apply a series of functions to an accumulating value. - This pattern promotes a declarative style, where you define what to do, not how to do it, leading to more readable, reusable, and maintainable code.
In our next lesson, we will dive deep into partial application and currying. These techniques are the key to unlocking the full potential of function composition, allowing you to seamlessly integrate functions of any number of arguments into your pipelines.