Currying in JavaScript Explained

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 multi‑argument 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 single‑argument 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 number‑adding 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: 5This 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: 5When 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: 12Here, 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: 102. 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: 6The 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 real‑world applications, too, like handling API requests or event listeners. To offer a more real‑world 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 foundHere, 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: 24Here, 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
curryhelper shown here is for fixed‑arity functions with a suitable argument count and no receiver dependency.
Currying turns a multi‑argument 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.