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
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
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
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>
);
};
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)
Why Should You Care?
- Modularity: You can create small, highly specific functions from large, generic ones.
-
Readability:
logError("Database failed")is much more readable thanlog("Error", "Server", "Database failed"). - Functional Composition: Currying is the backbone of "Piping," where the output of one function becomes the input for the next.
Top comments (0)