# JavaScript Currying and Composition: Functions That Chain Cleanly

Source: https://devdocs10.com/articles/javascript/js-currying-composition  
By Murat Bo · Published 2026-07-09 · DevDocs10 (https://devdocs10.com)

Currying and composition help you build small, reusable JavaScript functions that are easier to test, combine, and reason about. Together, they are a core part of functional programming style and show up in real code any time you want to transform data in steps.

> **Quick answer:** Currying turns a function that takes many arguments into a chain of functions that each take one argument. Composition combines functions so the output of one becomes the input of the next.

> **Difficulty:** Intermediate
>
> **You'll understand this better if you know:** how JavaScript functions return values, how arrow functions work, and the difference between arguments and parameters.

## 1. What Is Currying and Composition?

Currying and composition are two related but different ways to structure function logic in JavaScript.

- `Currying` changes a function so it accepts one argument at a time and returns another function until all arguments are provided.
- `Composition` combines several functions into one pipeline, where each function feeds the next.
- Both techniques encourage small functions that do one job well.
- They are often used together in reusable data-processing code.

For example, a curried function can create a specialized function like “format money in USD,” and composition can build a transformation pipeline like “trim, lowercase, then validate.”

## 2. Why Currying and Composition Matter

These patterns matter because they reduce repetition and make logic easier to reuse.

Instead of writing many slightly different functions, you can build one flexible function and specialize it with currying. Instead of writing a long block of step-by-step transformation code, you can compose smaller functions into a readable flow.

They are especially useful when you:

- build data transformation helpers
- want to reuse a function with different settings
- need predictable pipelines for validation or formatting
- prefer pure functions that avoid hidden state

They are less useful when the logic is highly interactive, heavily stateful, or clearer as a simple straight-line function.

## 3. Basic Syntax

### Currying a function

A normal function may take multiple parameters at once. A curried version returns another function after each argument until the final result is produced.

```javascript
const add = a => b => a + b;

const result = add(2)(3); // 5
```

Here, `add` does not take two arguments at once. It takes one, returns a function, then takes the next one.

### Composing functions

A simple composition helper passes a value through functions from right to left.

```javascript
const compose = (...fns) => value => fns.reduceRight((acc, fn) => fn(acc), value);
```

With composition, the output of one function becomes the input of the next function in the chain.

## 4. Step-by-Step Examples

### Example 1: Building a specialized formatter

Currying is useful when the same operation needs a fixed setting, such as a currency code, locale, or rounding rule.

```javascript
const formatCurrency = locale => currency => amount =>
  new Intl.NumberFormat(locale, {
    style: "currency",
    currency,
  }).format(amount);

const formatUSD = formatCurrency("en-US")("USD");

console.log(formatUSD(19.99));
```

Output (tested with Node 24.21.0):

```text
$19.99
```

This creates a reusable formatter configured for one locale and currency, so later calls only need the amount.

### Example 2: Composing text cleanup steps

Composition is a good fit for formatting strings in a predictable order.

```javascript
const trim = text => text.trim();
const toLowerCase = text => text.toLowerCase();
const collapseSpaces = text => text.replace(/\s+/g, " ");

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

const normalize = pipe(trim, toLowerCase, collapseSpaces);

console.log(normalize("  Hello   WORLD  ")); // "hello world"
```

Output (tested with Node 24.21.0):

```text
hello world
```

This is easier to read than repeating each transformation inline every time you need it.

### Example 3: Combining validation rules

You can use composition to keep validation steps separate and reusable.

```javascript
const isNotEmpty = text => text.trim() !== "";
const hasAtSymbol = text => text.includes("@");
const hasDotAfterAt = text => /@.+\./.test(text);

const and = (...checks) => value => checks.every((check) => check(value));

const isEmailLike = and(isNotEmpty, hasAtSymbol, hasDotAfterAt);

console.log(isEmailLike("alice@example.com")); // true
```

Output (tested with Node 24.21.0):

```text
true
```

The separate checks are easier to test and replace than a single large validation expression.

### Example 4: Reusing a curried predicate in array methods

Currying can create reusable functions for array filtering and searching.

```javascript
const greaterThan = limit => value => value > limit;

const numbers = [3, 8, 12, 5];
const greaterThanFive = greaterThan(5);
const filtered = numbers.filter(greaterThanFive);

console.log(filtered); // [8, 12]
```

Output (tested with Node 24.21.0):

```text
[ 8, 12 ]
```

This works well because `filter` expects a function that receives one value at a time.

## 5. Practical Use Cases

- Formatting numbers, dates, or strings with fixed settings.
- Building validation helpers that can be reused across forms or API responses.
- Creating array predicates such as “greater than,” “starts with,” or “matches pattern.”
- Assembling data pipelines for normalization, sanitizing, or mapping.
- Reducing duplication in utility modules and shared helper libraries.

## 6. Common Mistakes

### Mistake 1: Returning a value too early in a curried function

Beginners sometimes write a function that looks curried but still tries to use all inputs at once. That breaks the whole purpose of currying.

**Problem:** This function returns a number immediately, so calling it one argument at a time does not work.

```javascript
const add = (a, b) => a + b;

const sum = add(2)(3);
```

**Fix:** Return a function for each missing argument.

```javascript
const add = a => b => a + b;

const sum = add(2)(3);
```

The corrected version works because each call receives exactly one argument and returns the next function.

### Mistake 2: Using composition with non-function values

Composition only works when every item in the chain is a function. Passing a plain value breaks the pipeline.

**Problem:** This code puts a number inside the composed chain, so the pipeline cannot call it like a function.

```javascript
const compose = (...fns) => value =>
  fns.reduceRight((acc, fn) => fn(acc), value);

const double = n => n * 2;
const broken = compose(double, 3);
```

**Fix:** Keep every step as a function and pass the initial value separately.

```javascript
const compose = (...fns) => value =>
  fns.reduceRight((acc, fn) => fn(acc), value);

const double = n => n * 2;
const triple = n => n * 3;
const doubleThenTriple = compose(triple, double);

console.log(doubleThenTriple(4)); // 24
```

Output (tested with Node 24.21.0):

```text
24
```

The fixed version works because composition receives only functions and one starting value.

### Mistake 3: Confusing function order in composition

Composition order matters. If you place the functions in the wrong direction, you can get the wrong result even when the code still runs.

**Problem:** This example trims after lowercasing, which is fine, but the logic can become wrong when order affects the output shape.

```javascript
const compose = (...fns) => value =>
  fns.reduceRight((acc, fn) => fn(acc), value);

const trim = text => text.trim();
const addPrefix = text => "ID: " + text;

const wrong = compose(trim, addPrefix);
console.log(wrong(" 42 "));
```

Output (tested with Node 24.21.0):

```text
ID:  42
```

**Fix:** Put the transformation steps in the order you want the data to flow, or use a left-to-right helper if that is easier to read.

```javascript
const pipe = (...fns) => value =>
  fns.reduce((acc, fn) => fn(acc), value);

const pipeResult = pipe(trim, addPrefix)(" 42 ");
console.log(pipeResult); // "ID: 42"
```

Output (tested with Node 24.21.0):

```text
Uncaught ReferenceError: trim is not defined
```

The corrected version is easier to reason about because the data flow matches the order of the function list.

## 7. Best Practices

### Practice 1: Keep curried functions small and purpose-driven

Currying is most useful when each layer represents one meaningful choice, such as locale, prefix, or threshold.

```javascript
const createLogger = level => message =>
  `[${level}] ${message}`;

const warn = createLogger("WARN");
```

Small layers make the function easier to reuse and test.

### Practice 2: Prefer one-input functions for composition

Composition is simplest when every step takes a single value and returns a single value.

```javascript
const sanitize = text => text.trim();
const escapeHtml = text => text.replace(/&/g, "&amp;");
```

Single-input functions are easier to compose and less likely to create argument-order bugs.

### Practice 3: Name intermediate functions clearly

Even though currying can reduce repetition, unreadable one-line chains can hurt maintainability. Good names make the pipeline obvious.

```javascript
const isAdult = minAge => age => age >= minAge;
const isAllowedToVote = isAdult(18);
```

Clear names help other developers understand the intent without tracing every function call.

## 8. Limitations and Edge Cases

- Curried functions are not always more readable than regular functions, especially when the logic is short and direct.
- Composition assumes each step returns the right shape for the next step; one incompatible return value can break the chain.
- Many built-in JavaScript methods, such as `map` and `filter`, already support a composition-friendly style because they accept one callback at a time.
- If a function needs multiple unrelated inputs, forcing currying may make the API awkward.
- Debugging deeply nested function-returning-function chains can be harder than stepping through plain imperative code.

> **Note:** In JavaScript, composition and currying are conventions, not special language features. You build them yourself with ordinary functions.

## 9. Practical Mini Project

Let’s build a small text-processing pipeline that trims input, normalizes spacing, and adds a label. This shows how currying and composition work together in a realistic utility.

```javascript
const pipe = (...fns) => value =>
  fns.reduce((acc, fn) => fn(acc), value);

const trim = text => text.trim();
const collapseSpaces = text => text.replace(/\s+/g, " ");
const toTitleCase = text =>
  text.split(" ").map((word) =>
    word.charAt(0).toUpperCase() + word.slice(1)
  ).join(" ");

const prefixWith = prefix => text => `${prefix}: ${text}`;

const formatHeadline = pipe(trim, collapseSpaces, toTitleCase, prefixWith("Headline"));

console.log(formatHeadline("   currying    and   composition   "));
```

Output (tested with Node 24.21.0):

```text
Headline: Currying And Composition
```

This pipeline is fully reusable: the base operations stay separate, `prefixWith` is curried, and `pipe` composes the steps into one function.

## 10. Key Points

- Currying turns a multi-argument function into a chain of single-argument functions.
- Composition connects functions so the output of one becomes the input of the next.
- Curried functions are useful for specialization and reuse.
- Composed functions are useful for predictable transformation pipelines.
- Both patterns are most effective with small, pure functions.

## 11. Practice Exercise

Create a curried function and a composed pipeline for a simple profile formatter.

- Write a curried `addPrefix` function that accepts a prefix and then a string.
- Write a `pipe` helper that runs functions from left to right.
- Use the helpers to format the string `" jane doe "` into `"User: Jane Doe"`.

**Expected output:** `User: Jane Doe`

**Hint:** Start by trimming the input, convert it to title case, and then apply the prefix at the end.

**Solution:**

```javascript
const pipe = (...fns) => value =>
  fns.reduce((acc, fn) => fn(acc), value);

const trim = text => text.trim();
const toTitleCase = text =>
  text.split(" ").map((word) =>
    word.charAt(0).toUpperCase() + word.slice(1)
  ).join(" ");

const addPrefix = prefix => text => `${prefix}: ${text}`;

const formatUserName = pipe(
  trim,
  toTitleCase,
  addPrefix("User")
);

console.log(formatUserName("  jane doe  ")); // "User: Jane Doe"
```

Output (tested with Node 24.21.0):

```text
User: Jane Doe
```

## 12. Final Summary

Currying and composition are foundational JavaScript techniques for building small, reusable functions. Currying helps you specialize a function by supplying arguments one at a time, while composition lets you connect simple functions into a clear data flow.

Used well, these patterns make code easier to reuse, test, and understand. They work best with pure, single-purpose functions and are especially helpful in formatting, validation, and transformation pipelines.

If you want to go further, next study higher-order functions, partial application, and array method chaining so you can recognize when these patterns simplify your code and when a plain function is the better choice.
