JavaScript Currying and Composition
Transform multi-argument functions into chained calls and combine small functions into pipelines.
TL;DR
- 01Transform multi-argument functions into nested single-argument
curryfunctions. - 02Apply subset arguments up front to generate specialized function presets.
- 03Compose independent single-input operations into clean linear data pipelines.
Tips
- 01Design small, single-purpose functions to make currying and function composition patterns easier to build.
- 02Prefer the
pipehelper to construct left-to-right processing streams matching natural reading orders.
Warnings
- 01Avoid currying every simple method because unnecessary function wrappers hurt overall script readability.
- 02Validate data parameters at each pipeline step to prevent silent runtime type errors.
What Currying Does
Currying chainsConverts multi-argument functions into sequential single-argument calls.
function add(a, b, c) {
return a + b + c;
}
const curryAdd = a => b => c => a + b + c;
curryAdd(1)(2)(3); // 6Call executionInvokes the original function only after all arguments are received.
const add5 = curryAdd(5);
add5(2)(3); // 10Arrow notationUses nested arrow functions to construct inline curried definitions.
const multiply = a => b => a * b;A Generic Curry Helper
curry() implementationGathers arguments recursively until the count matches function arity.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...next) => {
return curried.apply(this, [...args, ...next]);
};
};
}Function lengthResolves function arity dynamically using the fn.length property.
const sum = curry((a, b, c) => a + b + c);
sum(1)(2)(3); // 6
sum(1, 2)(3); // 6Arity configurationsSpecifies manual arity limits for functions with default parameters.
function curryN(fn, arity) {
return function collect(...args) {
return args.length >= arity
? fn(...args)
: (...more) => collect(...args, ...more);
};
}Partial Application
bind() methodBinds arguments up front using the built-in prototype bind method.
function mult(a, b) { return a * b; }
const double = mult.bind(null, 2);
double(5); // 10Custom partialBuilds a partial helper to bind arguments without bind context.
function partial(fn, ...fixed) {
return (...rest) => fn(...fixed, ...rest);
}
const greet = partial((g, n) => g + n, "Hi ");
greet("Ada"); // "Hi Ada!"Composing Functions
compose() helperCombines functions executing right-to-left like mathematical equations.
const compose = (...fns) => x =>
fns.reduceRight((acc, f) => f(acc), x);
const shout = s => s.toUpperCase() + "!";
const format = compose(shout, s => s.trim());
format(" hi "); // "HI!"pipe() helperCombines functions executing left-to-right to match reading order.
const pipe = (...fns) => x =>
fns.reduce((acc, f) => f(acc), x);
const run = pipe(s => s.trim(), s => s.toUpperCase());
run(" hi "); // "HI"Practical Use Cases
Curried validationPre-fills validation parameters to generate specialized rule checklists.
const minLength = curry((min, s) => s.length >= min);
const isValid = minLength(3);
isValid("ok"); // falseMiddleware executionThreads orders or values through sequential modifier function lists.
const processOrder = pipe(
applyDiscount,
addTax
);
processOrder(100);In Practice
Pairs a curried discount calculator with a left-to-right pipeline to process invoice figures.
- 01Write a generic currying helper to allow step-by-step argument input.
- 02Curry the discount calculation function and fix the percentage rate.
- 03Define standard tax and currency formatting math operations.
- 04Pipe the functions together to thread inputs through each step sequentially.
- 05Invoke the completed pipeline with a test order price.
const curry = fn => (...args) =>
args.length >= fn.length
? fn(...args)
: (...more) => curry(fn)(...args, ...more);
const applyDiscount = curry((rate, price) =>
price * (1 - rate)
);
const applyTenPercentOff = applyDiscount(0.1);
const addTax = price => price * 1.08;
const format = price => "$" + price.toFixed(2);
const pipe = (...fns) => x =>
fns.reduce((acc, fn) => fn(acc), x);
const processOrder = pipe(
applyTenPercentOff,
addTax,
format
);
console.log(processOrder(100)); // "$97.20"FAQ
Currying transforms a function to accept one argument per call sequentially. Partial application binds multiple arguments immediately and returns a function waiting for the remaining parameters.
Inspect target function arity using the length property. Recursively gather arguments until they match or exceed that length, then invoke the underlying function.
Both helpers chain functions together. Compose executes functions from right to left. Pipe executes them from left to right, matching sequential reading order.
Currying enables specialized helper construction by pre-filling configuration values. This reduces duplicate parameters and allows seamless integration inside pipeline composition chains.
Curried calls instantiate closure environments and nested calls. The performance cost is usually negligible. Avoid currying inside critical performance paths like tight execution loops.