Currying & Partial Application
Currying isn't academic â it's how you build composable utility layers. Every time you write const add5 = add(5) or const withAuth = withHeaders({ Authorization: token }), you're using partial application powered by closures.
Currying vs Partial Application
These are related but distinct concepts:
Currying: f(a, b, c) â f(a)(b)(c)
Partial Application: f(a, b, c) â g(c) where a, b are pre-filledCurrying transforms a function so each argument is consumed one at a time. Partial application pre-fills some arguments and returns a function awaiting the rest.
Manual Currying
function multiply(a) {
return function(b) {
return a * b;
};
}
const double = multiply(2);
const triple = multiply(3);
double(5); // 10
triple(5); // 15With arrow functions, curried factories become concise:
const multiply = a => b => a * b;
const add = a => b => a + b;
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const transform = pipe(
add(1),
multiply(2),
multiply(3),
);
transform(4); // ((4 + 1) * 2) * 3 = 30Generic Curry Implementation
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, ...nextArgs]);
};
};
}
const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
add(1)(2, 3); // 6
add(1, 2, 3); // 6Edge Cases in the Implementation
fn.lengthcounts only parameters before the first default or rest parameter- Variadic functions (
...args) havelengthof 0 â curry breaks on them thisbinding must be preserved for method currying
function curryWithArity(fn, arity = fn.length) {
return function curried(...args) {
if (args.length >= arity) {
return fn.apply(this, args);
}
return function(...nextArgs) {
return curried.apply(this, [...args, ...nextArgs]);
};
};
}
const sum = curryWithArity((...nums) => nums.reduce((a, b) => a + b, 0), 3);
sum(1)(2)(3); // 6Partial Application with bind
JavaScript has built-in partial application via Function.prototype.bind:
function log(level, timestamp, message) {
console.log(`[${level}] ${timestamp}: ${message}`);
}
const warn = log.bind(null, 'WARN');
const warnNow = warn.bind(null, Date.now());
warn(Date.now(), 'Disk space low');
warnNow('Disk space low');bind pre-fills from left to right. For right-to-left or arbitrary position filling, you need a custom partial:
const PLACEHOLDER = Symbol('placeholder');
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
const args = presetArgs.map(arg =>
arg === PLACEHOLDER ? laterArgs.shift() : arg
);
return fn(...args, ...laterArgs);
};
}
const div = (a, b) => a / b;
const halve = partial(div, PLACEHOLDER, 2);
halve(10); // 5Real-World Patterns
API Client Factory
const createFetcher = (baseUrl: string) => (path: string) =>
(options?: RequestInit) => fetch(`${baseUrl}${path}`, options);
const api = createFetcher('https://api.example.com');
const getUsers = api('/users');
const getPosts = api('/posts');
await getUsers();
await getUsers({ headers: { Authorization: `Bearer ${token}` } });Event Handler Factories
const handleFieldChange = (field: string) => (e: ChangeEvent<HTMLInputElement>) => {
setForm(prev => ({ ...prev, [field]: e.target.value }));
};
<input onChange={handleFieldChange('email')} />
<input onChange={handleFieldChange('name')} />Middleware Composition
const withLogging = next => action => {
console.log('dispatching', action);
const result = next(action);
console.log('next state', store.getState());
return result;
};
const withCrashReporting = next => action => {
try {
return next(action);
} catch (err) {
reportError(err);
throw err;
}
};Performance Considerations
Currying creates intermediate closures. In hot paths, a single function with all arguments is faster:
// Hot path â avoid currying
for (let i = 0; i < 1_000_000; i++) {
result += multiply(2)(i); // creates closure per iteration
}
// Better â use the pre-applied version
const double = multiply(2);
for (let i = 0; i < 1_000_000; i++) {
result += double(i); // reuses single closure
}Interview Signal
Senior candidates demonstrate:
- Clarity on curry vs partial â They're related but not the same; curry is strict one-arg-at-a-time, partial is flexible pre-filling
- Implementation depth â Handling
fn.length, variadic functions,thisbinding, placeholder support - Practical application â API factories, event handlers, middleware, composition pipelines
- Trade-off awareness â Closure overhead in hot paths, readability vs abstraction balance