Currying in JavaScript Explained

Abstract image used to represent Currying in JavaScript Explained
Image by Taylor Kiser.

Currying is useful when fixing one argument now makes a function meaningfully reusable later. A formatter can capture a locale, for example, and return a smaller function for individual values. Without that practical need, turning every multiargument function into a chain of unary functions usually makes ordinary JavaScript harder to read.


What is Currying?

Starting at the very beginning, what even is currying? Essentially (and I apologise, you've already read this above), currying converts a function that accepts multiple arguments into a sequence of singleargument functions. Instead of calling a function with all the arguments at once, you can pass them to each function, one at a time.

It's much easier to understand with an example (or two), so...

An Example Without Currying

Here, we have a classic numberadding function, which accepts two arguments at once, and returns the sum of them both:

const add = (a: number, b: number): number => a + b;
console.log(add(2, 3));  // Output: 5

This is a very generic and standard way of defining functions in JavaScript, where all parameters are passed together.

An Example with Currying

With currying, on the other hand, the same functionality is transformed so that the function accepts arguments one at a time:

const curriedAdd = (a: number) => (b: number) => a + b;
console.log(curriedAdd(2)(3));  // Output: 5

When curriedAdd(2) is called, it returns a new function which then accepts a single argument, b. The outer call has already run; the returned inner function is waiting for b to be provided. When we call curriedAdd(2)(3), the inner function receives 3 and finally computes 2 + 3, returning 5.

This structure is useful because it allows partial application, which means that we can call curriedAdd(2) once and then reuse the returned function multiple times with different values for b.

For example:

const addTwo = curriedAdd(2);
console.log(addTwo(3));  // Output: 5
console.log(addTwo(10));  // Output: 12

Here, curriedAdd(2) returns a new function that always adds 2 to whatever value its input is, making it reusable for adding different values of b.

This is an incredibly simplistic example, but, hopefully, it will help set the groundwork for more complicated examples to follow...


Benefits of Currying

1. Function Reusability

Currying allows us to create specialised versions of functions easily. For example:

const multiply = (a: number) => (b: number) => a * b;
const double = multiply(2);
console.log(double(5));  // Output: 10

2. Avoiding Repetitive Code

It helps avoid repeating function calls with common arguments.

const greet = (greeting: string) => (name: string) => `${greeting}, ${name}!`;
const sayHello = greet("Hello");
console.log(sayHello("Ellie"));  // Output: Hello, Ellie!

3. Function Composition

Currying makes it easier to compose functions by chaining operations rather than repeatedly calling the same function, with (some of) the same values each time.


Implementing a Generic Curry Function

For a function with a fixed number of required arguments, this helper collects arguments until it has enough to call the original function:

const curry = (fn: Function) => {
  return function curried(...args: any[]) {
    return args.length >= fn.length
      ? fn.apply(null, args)
      : (...nextArgs: any[]) => curried(...args, ...nextArgs);
  };
};

const sum = (a: number, b: number, c: number) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3));  // Output: 6

The helper uses fn.length to decide when to call the function. It accepts several arguments at one step as well as one at a time, so it is more flexible than strictly unary currying. Keep it to functions whose length matches the number of arguments you intend to collect: default parameters and rest parameters change that count. It also calls the function with a null receiver, so functions that depend on this need a separate receiver policy.


A Real‑World Example of Currying

So far in this article, we've focused on currying with numbers because it's a fairly straightforward way to demonstrate what's going on. However, currying is particularly useful in realworld applications, too, like handling API requests or event listeners. To offer a more realworld example, consider a scenario where we need to log user actions with different levels of severity. Using currying, we can achieve this like this:

const logMessage = (level: string) => (component: string) => (message: string) => 
  console.log(`[${level}] (${component}): ${message}`);

const errorLogger = logMessage("ERROR");
const authLogger = errorLogger("AuthModule");

authLogger("Invalid password attempt");
// Output: [ERROR] (AuthModule): Invalid password attempt

authLogger("User not found");
// Output: [ERROR] (AuthModule): User not found

Here, logMessage is curried so that we can create specialised loggers. The first function fixes the severity level, the second fixes the component, and the final function logs the message. This structure allows flexible and reusable logging without repeatedly passing the same arguments to the logger.

Coincidentally, this is actually a pattern I often use when logging API calls and behaviours in my Next.js projects.


Currying vs. Partial Application

Currying

  • Always breaks down a function into unary functions (one argument at a time).
  • Requires all arguments, eventually.

Partial Application

  • Fixes some arguments whilst keeping the function callable with fewer parameters.
const partial = (fn: Function, ...presetArgs: any[]) => (...laterArgs: any[]) => fn(...presetArgs, ...laterArgs);

const multiply = (a: number, b: number, c: number) => a * b * c;
const multiplyByTwo = partial(multiply, 2);
console.log(multiplyByTwo(3, 4));  // Output: 24

Here, partial allows setting initial arguments whilst still leaving others open for later use.


When to Use Currying

  • Enhancing reusability

    : Ideal for setting predefined arguments in frequently used functions.
  • Functional programming

    : Works well with composition techniques.
  • Improving readability

    : Can make complex functions more manageable and declarative.

Wrapping Up

Key Takeaways

  • Currying converts a function into a series of functions, each taking a single argument.
  • It improves function reusability and composability.
  • It differs from partial application, which fixes some arguments but keeps the function callable with the rest.
  • The curry helper shown here is for fixedarity functions with a suitable argument count and no receiver dependency.

Currying turns a multiargument function into a sequence of functions that each receive one argument. Partial application is broader: it fixes one or more arguments and returns something callable with the rest. In the logging example, each step earns its place by creating a useful specialised function; where there is no useful intermediate function, the ordinary call is clearer.


Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.