What Are Currying and Function Composition? A Guide to Modular JavaScript Code

Currying and function composition are functional programming techniques that break complex logic into reusable, single-purpose units by transforming multi-argument functions into chains of unary functions and wiring them together into explicit data pipelines.

The 33-js-concepts repository by leonardomso demonstrates these patterns with production-ready implementations in tests/functional-programming/currying-composition/currying-composition.test.js, showing how they eliminate boilerplate and create declarative code flows.

Understanding Currying in JavaScript

What Is Currying?

Currying converts a function that accepts multiple arguments into a sequence of nested functions, each taking exactly one argument. Each intermediate function returns another function awaiting the next argument until all parameters are satisfied.

In the 33-js-concepts codebase, a manual curried function looks like this:

// Basic currying – one argument at a time (L7-L8)
const add = a => b => c => a + b + c;

add(1)(2)(3); // → 6

This pattern enables partial application, where you fix some arguments upfront and reuse the resulting specialized function later.

The Curry Implementation in 33-js-concepts

The repository provides a generic curry helper that works with functions of any arity. Located at lines 93–101 in currying-composition.test.js, the implementation recursively accumulates arguments until the original function's length is met:

// Arbitrary-arity curry helper (L93-L101)
const curry = fn => {
  return function curried(...args) {
    return args.length >= fn.length
      ? fn(...args)
      : (...next) => curried(...args, ...next);
  };
};

const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);

curriedSum(1)(2)(3); // → 6
curriedSum(1, 2)(3); // → 6 (flexible invocation)

A simpler two-argument version appears at lines 75–84 for cases where you only need binary operations.

Partial Application with Curried Functions

Once a function is curried, you can create reusable presets by supplying initial arguments. According to the test suite at lines 87–89, this pattern replaces repetitive inline logic:

// Reusing a curried function (L87-L89)
const add5 = add(5);
add5(10); // → 15
add5(20); // → 25

This technique appears throughout the companion file tests/functional-programming/vanilla-js-utility-functions/vanilla-js-utility-functions.test.js, where curried utilities like map and filter are composed into domain-specific operations.

Function Composition Patterns

Left-to-Right with Pipe

Function composition chains unary functions so the output of one becomes the input of the next. The pipe helper (lines 35–36) executes functions left-to-right, matching natural reading order:

// Pipe – left-to-right composition (L35-L36)
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

const process = pipe(
  x => x + 1,
  x => x * 2,
  x => x * x
);

process(5); // → 144 (5 → 6 → 12 → 144)

Right-to-Left with Compose

For "data-last" thinking, the repository offers compose (lines 64–65), which folds functions from right to left using reduceRight:

// Compose – right-to-left composition (L64-L65)
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);

const composed = compose(
  x => x * x,
  x => x * 2,
  x => x + 1
);

composed(5); // → 144 (same calculation, reversed order)

Building Modular Code with Currying and Composition

Reusable Utility Functions

Combining curried helpers with pipelines creates declarative data transformations without inline boilerplate. The vanilla JS utilities test file demonstrates this with array operations:

// Curried utilities ready for composition
const map = fn => arr => arr.map(fn);
const filter = pred => arr => arr.filter(pred);

const double = x => x * 2;
const isEven = x => x % 2 === 0;

const doubleEvens = pipe(
  filter(isEven),
  map(double)
);

doubleEvens([1, 2, 3, 4]); // → [4, 8]

Here, map and filter are curried to accept their behavioral argument first, returning functions awaiting the data array. This data-last style maximizes reusability across different collections.

Real-World API Client Example

The test suite includes a practical URL builder (lines 40–43) showing how currying creates configurable API clients:

// Data-last API client configuration (L40-L43)
const createApiUrl = base => endpoint => params => {
  const qs = new URLSearchParams(params).toString();
  return `${base}${endpoint}${qs ? '?' + qs : ''}`;
};

const github = createApiUrl('https://api.github.com');
const usersEndpoint = github('/users');

usersEndpoint({ per_page: 10 }); // → 'https://api.github.com/users?per_page=10'

By currying createApiUrl, you lock in the base domain once, then reuse the specialized functions for different endpoints and query parameters without repeating the base URL logic.

Summary

  • Currying transforms multi-argument functions into sequences of single-argument functions, enabling partial application and argument presetting as shown in currying-composition.test.js lines 93–101.
  • Function composition via pipe (L35–L36) and compose (L64–L65) creates explicit data pipelines that read like natural language, making control flow obvious.
  • Together, these techniques generate tiny, single-purpose functions that combine without rewriting boilerplate, improving reusability across the codebase.
  • The 33-js-concepts repository provides canonical implementations that work with arbitrary function arities and integrate seamlessly with JavaScript's native array methods.

Frequently Asked Questions

What is the difference between currying and partial application?

Currying is the transformation process itself—converting a function of N arguments into N functions of one argument each. Partial application is the technique of fixing a number of arguments to a function, producing another function of smaller arity. In the 33-js-concepts examples, currying enables partial application by allowing you to call add(5) and get back a reusable function waiting for the remaining arguments.

When should I use pipe versus compose?

Use pipe when you want functions to execute in the order they appear left-to-right, which aligns with how JavaScript reads chronologically. Use compose when you prefer right-to-left execution, often matching mathematical notation or "data-last" functional styles. The repository implements both in currying-composition.test.js so you can choose based on team conventions or specific algorithm clarity.

How does currying improve testability?

Curried functions isolate single responsibilities, making unit tests straightforward. Because each step returns a pure function, you can test intermediate transformations independently without mocking entire dependency graphs. The 33-js-concepts test suite itself leverages this modularity to verify curry, pipe, and compose helpers in isolation before testing integrated pipelines.

Can currying be used with existing JavaScript array methods?

Yes, by wrapping native methods in curried wrappers. The repository demonstrates this in vanilla-js-utility-functions.test.js with patterns like const map = fn => arr => arr.map(fn). This adapts data-first methods to a data-last API, allowing them to slot directly into pipe or compose chains without imperative restructuring.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →