The Ultimate Guide to Function Currying in JavaScript

The Ultimate Guide to Function Currying in JavaScript

# javascript
The Ultimate Guide to Function Currying in JavaScriptkiran ravi

If you've spent time in the functional programming world or looked at the source code of libraries...

If you've spent time in the functional programming world or looked at the source code of libraries like Redux, you’ve likely encountered functions that look like this: const result = add(5)(10)(20);.

At first glance, it looks like a typo. But it’s actually one of the most powerful patterns in JavaScript: Function Currying.


1. What is Currying? (The Technical Definition)

Currying is a functional programming technique where a function that takes multiple arguments is transformed into a sequence of nesting functions, each taking a single argument.

Instead of: f(a, b, c)
You get: f(a)(b)(c)

Currying doesn’t actually call a function; it transforms it. The execution only happens once the last argument in the chain is provided.


2. How it Works: The Magic of Closures

Currying is only possible in JavaScript because of Closures. When the outer function is called, it returns the inner function. Even after the outer function has finished executing, the inner function "remembers" the variables in the outer scope.

The Evolution of a Function

The Standard Way:

const multiply = (a, b) => a * b;
console.log(multiply(2, 5)); // 10

Enter fullscreen mode Exit fullscreen mode

The Curried Way:

const curriedMultiply = (a) => {
    return (b) => {
        return a * b;
    };
};

const double = curriedMultiply(2); // 'a' is locked as 2
console.log(double(5)); // 10

Enter fullscreen mode Exit fullscreen mode

3. Real-World Use Case: The Configuration Pattern

The most practical way to think about Currying is as Function Configuration. You provide the "setup" arguments first and the "data" arguments later.

Example: Customizing an API Client

Imagine you are building a dashboard that fetches data from different services but uses the same API Key.

const apiCall = (apiKey) => (resource) => (id) => {
    return `Fetching ${resource} #${id} using key: ${apiKey}`;
};

// Step 1: Initialize with the API Key (Configuration)
const privateRequest = apiCall("SECRET_TOKEN_99");

// Step 2: Configure for specific resources
const getProduct = privateRequest("products");
const getUser = privateRequest("users");

// Step 3: Use in real-time
console.log(getProduct("A101")); // Fetching products #A101 using key: SECRET_TOKEN_99
console.log(getUser("007"));     // Fetching users #007 using key: SECRET_TOKEN_99

Enter fullscreen mode Exit fullscreen mode

4. Real-World Use Case: Modern UI React Events

In React, currying is frequently used to handle list items without creating an anonymous arrow function inside the render method, which can help with performance and readability.

const Shop = () => {
  // Curried handler
  const addToCart = (productId) => (event) => {
    console.log(`Item ${productId} added by clicking ${event.target.id}`);
  };

  const products = [{id: 'p1', name: 'Laptop'}, {id: 'p2', name: 'Mouse'}];

  return (
    <div>
      {products.map(p => (
        <button key={p.id} id="btn-add" onClick={addToCart(p.id)}>
          Add {p.name}
        </li>
      ))}
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

Note: addToCart(p.id) is executed during render, returning the actual function that onClick will use.


5. Currying vs. Partial Application

These terms are often used interchangeably, but there is a technical distinction:

  • Currying: Always breaks a function down into series of unary (one-argument) functions.
  • f(a, b, c) -> f(a)(b)(c)

  • Partial Application: Fixes a subset of arguments and returns a function for the rest.

  • f(a, b, c) -> f(a)(b, c) (Note: the second function takes two arguments).

[Image comparing Function Currying versus Partial Application showing the number of arguments handled at each step]


6. Creating a Generic curry() Helper

In a professional environment, you won't always write functions in curried form. You can use a "Curry Utility" to transform existing functions.

function curry(fn) {
    return function curried(...args) {
        // If the number of arguments provided matches the original function...
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            // ...otherwise, return a new function to collect the rest
            return function(...nextArgs) {
                return curried.apply(this, args.concat(nextArgs));
            }
        }
    };
}

// Transform a standard function
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);

console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6 (Hybrid approach)

Enter fullscreen mode Exit fullscreen mode

Why Should You Care?

  1. Modularity: You can create small, highly specific functions from large, generic ones.
  2. Readability: logError("Database failed") is much more readable than log("Error", "Server", "Database failed").
  3. Functional Composition: Currying is the backbone of "Piping," where the output of one function becomes the input for the next.