Currying in JavaScript Explained

Hero image for Currying in JavaScript Explained. Image by Taylor Kiser.
Hero image for 'Currying in JavaScript Explained.' Image by Taylor Kiser.

Currying is a functional programming technique which transforms a function with multiple arguments into a series of functions, each accepting a single argument. This is a technique that originates from lambda calculus and is widely used in functional languages like Haskell and Lisp. In JavaScript, currying allows for better composition and reusability, aligning with its functional programming roots. Whilst JavaScript is not a purely functional language, it supports functional programming principles, making currying a valuable technique.

In this article, I will explore how currying works in JavaScript, its benefits, and how it compares to partial applications. Hopefully, you will understand how to implement currying and when to use it effectively.


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. At this stage, the function isn't executed yet; it is just 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: 5console.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 specialized 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

To apply currying dynamically to the code we're writing, we can write a helper function that might look like this:

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

What this does, is check to see if the expected number of arguments has been provided. If not, it returns a new function awaiting the remaining arguments.


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 attemptauthLogger("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 while 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

To summarise all of the above, currying is a powerful concept that transforms multiargument functions into a sequence of unary functions. It enables reusability, function composition, and cleaner code.

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.
  • A curry helper function can make any function curried dynamically.

Understanding and using currying effectively can help write cleaner, more reusable JavaScript code in functional programming contexts.


Categories:

  1. Development
  2. Front‑End Development
  3. JavaScript