Functional JavaScript: Build a Compose Utility from Scratch

Functional JavaScript: Build a Compose Utility from Scratch

Functional JavaScript: Build a Compose Utility from Scratch

 

Functional programming in JavaScript can drastically simplify your code by reducing side effects and enhancing composability. Whether you’re transforming data or building reusable utilities, mastering core concepts like function composition is invaluable. In this blog post, we’ll walk through creating a compose() utility from scratch and explore practical use cases including input sanitization pipelines.

1. What Is Function Composition?

In functional programming, composition means combining simple functions to build more complex ones. The idea is to pass the output of one function directly into another. Mathematically, this is written as f(g(x)), or compose(f, g)(x).

For example:

const greet = name => `Hello, ${name}!`;
const excited = str => `${str} 😄`;

const excitedGreeting = str => excited(greet(str));
console.log(excitedGreeting('Alice')); // Hello, Alice! 😄

This works well for 2–3 functions, but for longer pipelines, it quickly becomes hard to manage. That’s where a compose() utility becomes essential.

2. Building the compose() Function Step-by-Step

Let’s implement a basic version of compose() that takes any number of unary (single-argument) functions and returns a new function.

function compose(...fns) {
  return function (x) {
    return fns.reduceRight((acc, fn) => fn(acc), x);
  };
}

Explanation:

  • ...fns gathers all functions into an array.
  • reduceRight starts from the rightmost function (the innermost in traditional function nesting) and applies each function to the accumulator.

Usage example:

const trim = str => str.trim();
const toLower = str => str.toLowerCase();
const exclaim = str => str + '!';

const cleanMessage = compose(exclaim, toLower, trim);
console.log(cleanMessage('   HELLO WORLD   ')); // hello world!

3. Real-World Use Case: Input Sanitization Pipeline

A common use case for compose() is sanitizing user input, especially on form submissions or API requests.

Example: Standardizing user name input

const stripTags = str => str.replace(/<[^>]*>/g, '');
const capitalize = str =>
  str.replace(/\b\w/g, char => char.toUpperCase());

const sanitizeName = compose(capitalize, toLower, stripTags, trim);

console.log(sanitizeName('    <script>alert(1)</script>JOHN doe   '));
// John Doe

Pro Tips: Use composition to build reusable and declarative data-processing steps. Each function should ideally be pure and single-purpose.

4. Adding Flexibility with Pipe (Left-to-Right)

Developers sometimes prefer the flow to read left-to-right. That’s where pipe() comes in, which is the opposite of compose().

function pipe(...fns) {
  return function (x) {
    return fns.reduce((acc, fn) => fn(acc), x);
  };
}

This allows syntax like:

const sanitizeEmail = pipe(trim, toLower);
console.log(sanitizeEmail('   USER@DOMAIN.COM  ')); // user@domain.com

Tip: Choose pipe() or compose() depending on what makes your code more readable. Both are composition tools; just different directions.

5. Performance and Optimization Strategies

Function composition is not just syntactic sugar — it can improve performance when used wisely. However, you must also consider:

  • Avoid unnecessary functions: Each composed function adds an overhead. Keep your pipelines lean.
  • Memoization: Cache composed results if inputs are repeated often — use libraries like lodash.memoize to help.
  • Debugging: Break out intermediate results if debugging is painful. Inline composition can obscure bugs.

Example of debugging a composed chain:

const debug = label => val => {
  console.log(label, val);
  return val;
};

const process = compose(
  debug('Final result:'),
  exclaim,
  debug('After lower:'),
  toLower,
  debug('After trim:'),
  trim
);

console.log(process('   Hi THERE '));

This strategy helps trace where inputs are mutated.

Conclusion

By building your own compose() utility from scratch, you unlock the expressive power of functional programming in JavaScript. Whether you’re sanitizing input or transforming API results, composition gives you a clean, testable, and scalable pattern to work with. Try integrating these utilities in your own projects — and watch your codebase become more declarative and maintainable.

 

Useful links: