Skip to Content
Course content

110: Function Composition

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this pattern a dozen times in code reviews. A developer has a few small, focused utility functions, and they want to use them all on a single piece of data. It usually starts out looking something like this:

const trim = (str) => str.trim();
const lowercase = (str) => str.toLowerCase();
const removeSpecialChars = (str) => str.replace(/[^a-z0-9 ]/g, '');

const prepareSearchQuery = (input) => {
  return removeSpecialChars(lowercase(trim(input)));
};

console.log(prepareSearchQuery("  Hello World!!!  ")); // "hello world"

The "Inside-Out" Readability Problem

Now, technically, the code above works. But it's "broken" from a maintainability perspective. Notice how you have to read the logic from the inside out? To understand what happens to the string, your eyes have to jump to the middle—trim—then move left to lowercase, and then further left to removeSpecialChars.

It's a cognitive tax. If we decide we need to add two more steps—say, removing stop-words and adding a prefix—the prepareSearchQuery function becomes a nesting nightmare. You end up with a "pyramid of parentheses" that is incredibly easy to mess up with a single missing ).

Building a Pipeline for Data Flow

The fix is to stop nesting and start composing. In functional programming, composition is the act of combining simple functions to build more complex ones. Instead of manually wrapping functions, we can create a pipe utility. I prefer pipe over compose because it reads left-to-right (or top-to-bottom), which matches how we naturally think about data moving through a system.

Here is how I usually implement it:

const pipe = (...fns) => (initialValue) => 
  fns.reduce((acc, fn) => fn(acc), initialValue);

const trim = (str) => str.trim();
const lowercase = (str) => str.toLowerCase();
const removeSpecialChars = (str) => str.replace(/[^a-z0-9 ]/g, '');

// Now we define the logic as a sequence of steps
const prepareSearchQuery = pipe(
  trim,
  lowercase,
  removeSpecialChars
);

console.log(prepareSearchQuery("  Hello World!!!  ")); // "hello world"

Look at the difference. prepareSearchQuery is now just a list of operations. If I want to change the order or add a new step, I just add a line to the list. I don't have to touch any parentheses or worry about where the return statement is.

The magic here is in reduce. The pipe function takes a bunch of functions as arguments (the ...fns spread) and returns a new function. When that new function is called with an initialValue, it iterates through the array of functions, passing the result of the previous function as the input to the next one. It's essentially a conveyor belt for your data.




📋 Practical Task

Building a Price Formatting Pipeline

You are building a checkout page where raw price data from an API needs to be cleaned and formatted before being displayed to the user. The raw data is often messy (e.g., " 1250.5 ").

Your task is to create a formatPrice function using a pipe utility. Implement the following individual functions first:

  • trimWhitespace: Removes leading/trailing spaces.
  • convertToNumber: Converts the string to a float.
  • addCurrencySymbol: Takes a number and returns a string with a "$" prefix.
  • roundToTwoDecimals: Ensures the number has exactly two decimal places (use .toFixed(2)).

Requirements:

  1. Implement a pipe function that can handle any number of functions.
  2. Compose the formatPrice function so that the data flows in this order: Trim $\rightarrow$ Convert to Number $\rightarrow$ Round $\rightarrow$ Add Symbol.
  3. Test it with the input " 1250.5 " to ensure the final output is "$1250.50".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.