Currying and Partial Application
Currying and partial application are two techniques that leverage closures to create more flexible, reusable functions. They're often conflated, but they solve different problems in different ways.
Key Insights
- Currying transforms a function with multiple arguments into a sequence of functions that each take a single argument, while partial application fixes some arguments upfront and returns a function that accepts the remaining ones.
- Both techniques enable powerful code reuse patterns—create specialized functions from generic ones without duplicating logic or passing the same arguments repeatedly.
- Curried functions compose naturally in data pipelines, but the abstraction carries runtime overhead and debugging complexity that you should weigh against the benefits.
Functions That Return Functions
Currying and partial application are two techniques that leverage closures to create more flexible, reusable functions. They’re often conflated, but they solve different problems in different ways.
Currying transforms a function that takes multiple arguments into a chain of functions, each accepting exactly one argument. A curried add(a, b, c) becomes add(a)(b)(c).
Partial application fixes some arguments to a function now and returns a new function that accepts the remaining arguments. You might partially apply add(a, b, c) with a = 1 to get a function that takes b and c together.
Both techniques matter because they let you build specialized functions from generic ones. Instead of writing fetchUserData(), fetchOrderData(), and fetchProductData() as separate functions, you can partially apply a generic fetchData() with different endpoints. Instead of threading the same configuration object through a dozen function calls, you can bake it in once.
Understanding Currying
Currying converts a function with arity n into n nested functions of arity 1. Here’s what that looks like manually:
// Regular function
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3); // 6
// Manually curried version
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
curriedAdd(1)(2)(3); // 6
// Arrow function syntax makes this cleaner
const curriedAddArrow = a => b => c => a + b + c;
curriedAddArrow(1)(2)(3); // 6
The power becomes apparent when you don’t supply all arguments at once:
const addOne = curriedAddArrow(1); // Returns: b => c => 1 + b + c
const addOneAndTwo = addOne(2); // Returns: c => 1 + 2 + c
const result = addOneAndTwo(3); // Returns: 6
Each intermediate function captures the previous arguments in its closure. You can pass these intermediate functions around, store them, and invoke them later with the remaining arguments.
Writing curried functions manually is tedious. A generic curry helper automates the transformation:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
};
}
// Usage
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
curriedMultiply(2)(3)(4); // 24
curriedMultiply(2, 3)(4); // 24 - also works with multiple args
curriedMultiply(2)(3, 4); // 24 - flexible argument grouping
curriedMultiply(2, 3, 4); // 24 - all at once still works
This implementation checks if enough arguments have been collected. If so, it calls the original function. If not, it returns a new function that accumulates more arguments.
Understanding Partial Application
Partial application doesn’t require the single-argument chain structure. You fix whatever arguments you want, in whatever positions, and get back a function that accepts the rest.
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}
function greet(greeting, punctuation, name) {
return `${greeting}, ${name}${punctuation}`;
}
const greetHello = partial(greet, 'Hello', '!');
greetHello('Alice'); // "Hello, Alice!"
greetHello('Bob'); // "Hello, Bob!"
const greetCasual = partial(greet, 'Hey');
greetCasual('...', 'Charlie'); // "Hey, Charlie..."
JavaScript’s Function.prototype.bind() provides built-in partial application:
function logMessage(level, timestamp, message) {
console.log(`[${level}] ${timestamp}: ${message}`);
}
const logError = logMessage.bind(null, 'ERROR');
const logWarning = logMessage.bind(null, 'WARNING');
logError(Date.now(), 'Connection failed');
logWarning(Date.now(), 'Retry limit approaching');
The first argument to bind() sets this (use null if you don’t need it). Subsequent arguments are partially applied in order.
The key difference from currying: partial application doesn’t impose any structure on how arguments are supplied. You can fix the first two arguments and accept the last three together. Currying always produces single-argument functions in sequence.
Practical Use Cases
Configuration and Dependency Injection
Partial application shines when you have functions that share common configuration:
function createApiClient(baseUrl, headers) {
return {
get: partial(fetchData, baseUrl, headers, 'GET'),
post: partial(fetchData, baseUrl, headers, 'POST'),
put: partial(fetchData, baseUrl, headers, 'PUT'),
delete: partial(fetchData, baseUrl, headers, 'DELETE'),
};
}
function fetchData(baseUrl, headers, method, endpoint, body = null) {
return fetch(`${baseUrl}${endpoint}`, {
method,
headers,
body: body ? JSON.stringify(body) : null,
});
}
const api = createApiClient('https://api.example.com', {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json',
});
// Clean, focused calls - no repeated configuration
api.get('/users');
api.post('/orders', { item: 'widget', quantity: 5 });
Reusable Validation Functions
Build a library of validators from a generic validation function:
const validate = curry((predicate, errorMessage, value) => {
if (!predicate(value)) {
return { valid: false, error: errorMessage };
}
return { valid: true, value };
});
// Create specialized validators
const isRequired = validate(
v => v !== null && v !== undefined && v !== '',
'This field is required'
);
const isEmail = validate(
v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
'Invalid email format'
);
const minLength = n => validate(
v => v.length >= n,
`Must be at least ${n} characters`
);
const maxValue = n => validate(
v => v <= n,
`Must be at most ${n}`
);
// Use them
isRequired(''); // { valid: false, error: 'This field is required' }
isEmail('test@test.com'); // { valid: true, value: 'test@test.com' }
minLength(8)('short'); // { valid: false, error: 'Must be at least 8 characters' }
Event Handlers with Pre-bound Context
const handleClick = curry((analytics, userId, event) => {
analytics.track('click', {
userId,
target: event.target.id,
timestamp: Date.now(),
});
});
// In your component setup
const trackClick = handleClick(analyticsService)(currentUser.id);
button.addEventListener('click', trackClick);
// The handler already knows about analytics and userId
Currying in Functional Composition
Curried functions enable point-free style, where you compose functions without explicitly mentioning the data they operate on:
const curry = fn => (...args) =>
args.length >= fn.length
? fn(...args)
: curry(fn.bind(null, ...args));
const map = curry((fn, arr) => arr.map(fn));
const filter = curry((predicate, arr) => arr.filter(predicate));
const reduce = curry((fn, initial, arr) => arr.reduce(fn, initial));
const prop = curry((key, obj) => obj[key]);
const gt = curry((threshold, value) => value > threshold);
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
// Build a data pipeline
const processOrders = pipe(
filter(prop('isPaid')),
filter(pipe(prop('total'), gt(100))),
map(prop('id')),
reduce((sum, id) => sum + id, 0)
);
const orders = [
{ id: 1, total: 150, isPaid: true },
{ id: 2, total: 50, isPaid: true },
{ id: 3, total: 200, isPaid: false },
{ id: 4, total: 300, isPaid: true },
];
processOrders(orders); // 5 (sum of ids 1 and 4)
Each curried utility accepts its configuration first, then the data. This lets you build the entire pipeline as a composition of configured functions, then run data through it.
Performance and Tradeoffs
These techniques aren’t free. Every curried or partially applied function creates a closure that captures its arguments. Chain ten curried calls and you have ten nested closures. This adds memory overhead and function call overhead.
For hot paths processing millions of items, this matters. For application-level code handling user interactions or API calls, it’s negligible.
Debugging is the bigger concern. Stack traces through curried functions show anonymous functions and closure chains. When something goes wrong, you’re reading through layers of curried and partial wrappers to find the actual logic.
Use currying and partial application when:
- You’re building reusable utilities that get configured differently in different contexts
- You’re composing data transformation pipelines
- You’re working with functional libraries that expect curried functions
- The abstraction genuinely reduces complexity
Keep it simple when:
- The function is only called one way throughout your codebase
- You’re in performance-critical code
- Your team isn’t familiar with these patterns
- A plain function with default parameters would work just as well
Conclusion
Currying and partial application are tools for building flexible, composable code. Currying enforces a specific structure—single-argument functions all the way down—that enables elegant composition. Partial application is more pragmatic: fix what you know now, fill in the rest later.
Start with partial application when you find yourself passing the same first few arguments repeatedly. Graduate to currying when you’re building functional pipelines or working with libraries designed around it.
For production use, consider battle-tested implementations. Ramda provides auto-curried versions of common utilities. Lodash/fp offers a functional programming variant with curried, data-last functions. These handle edge cases and optimizations you’d otherwise have to figure out yourself.