-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
114: Practice Exercise: Building a Pipe/Compose Utility
When I first started digging into functional programming in JavaScript, I hit a wall with the concept of "piping." I kept thinking that a pipe utility was just a fancy wrapper for a for loop. I thought, "Why do I need a complex utility when I can just iterate over an array of functions and update a variable?"
Thinking a Pipe is just a loop
Here is the mistake I made. I wrote a "pipe" that looked something like this:
function runPipe(fns, value) {
let result = value;
for (const fn of fns) {
result = fn(result);
}
return result;
}
// Usage:
const trim = s => s.trim();
const uppercase = s => s.toUpperCase();
runPipe([trim, uppercase], " hello world "); // "HELLO WORLD"
Technically, this works. It gets the job done. But it's not actually a pipe utility in the software engineering sense—it's just a runner. The problem here is that the data (the string) and the logic (the functions) are tied together immediately. I can't reuse that specific pipeline elsewhere in my app without passing the data in every single time. In a real production environment, you often want to define the transformation logic once and then apply it to a stream of data later.
Creating a Function Factory
The "aha!" moment for me was realizing that pipe should be a higher-order function. It shouldn't execute the logic immediately; it should return a new function that embodies the pipeline. This allows you to define a specific process (like a data sanitizer) and pass that process around as a first-class citizen.
The cleanest way to implement this in modern JavaScript is using reduce. Instead of managing a let variable in a loop, we use the accumulator to pass the result of one function into the next.
const pipe = (...fns) => (initialValue) =>
fns.reduce((acc, fn) => fn(acc), initialValue);
// Now, look at the difference in usage:
const trim = s => s.trim();
const uppercase = s => s.toUpperCase();
const exclaim = s => `${s}!`;
// We create a specialized function here. No data has been processed yet.
const prepareGreeting = pipe(trim, uppercase, exclaim);
// Now we can use that "pipeline" whenever we want
console.log(prepareGreeting(" hello ")); // "HELLO!"
console.log(prepareGreeting(" good morning ")); // "GOOD MORNING!"
I personally love this pattern because it reads like a recipe. You define the steps from top to bottom (or left to right), and the logic remains decoupled from the data it eventually processes.
The Mirror Image: Compose
You'll often hear compose mentioned alongside pipe. They are mathematically the same, but the execution order is flipped. While pipe goes left-to-right, compose goes right-to-left. This is a holdover from mathematical notation f(g(x)), where the innermost function executes first.
To build a compose utility, you only need to change one thing: the order of the functions. Using reduceRight instead of reduce flips the execution flow perfectly.
const compose = (...fns) => (initialValue) =>
fns.reduceRight((acc, fn) => fn(acc), initialValue);
// With compose, the LAST function listed is the FIRST one to run.
const processText = compose(exclaim, uppercase, trim);
// trim -> uppercase -> exclaim
In my experience, pipe is much more intuitive for most developers because we read from left to right, but you'll see compose everywhere if you start using libraries like Redux. Now that you've seen how the factory pattern works, let's put it into practice.
📋 Practical Task
Exercise: Building a User Profile Sanitizer Pipeline
You are building a user registration system. Before the user data is saved to the database, it needs to go through a series of cleaning steps. Your goal is to create a compose utility and use it to build a sanitization pipeline.
Requirements:
- Implement a
composefunction that takes any number of functions as arguments and returns a new function. - Create three small helper functions:
trimWhitespace: Removes leading and trailing spaces from a string.toLowerCase: Converts a string to lowercase.escapeHtml: Replaces the<character with<and the>character with>.
- Use your
composeutility to create a function calledsanitizeInput. Crucially, because you are usingcompose, you must list the functions in the order that they should be executed last to first. - Test your
sanitizeInputfunction with the string:" <script>alert('hi')</script> ". The result should be trimmed, lowercased, and have the HTML characters escaped.
// Your code here:
There are no comments for now.