Currying Get Certified
Currying is a technique where a function that takes multiple arguments is transformed into a sequence of functions that each take a single argument. Instead of calling add(1, 2, 3), a curried version is called as add(1)(2)(3).
Manual Currying
What this code does: Manually turns a multi-argument function into a chain of single-argument functions.
function add(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
console.log("add(1)(2)(3) =", add(1)(2)(3));
const addOne = add(1);
const addOneAndTwo = addOne(2);
console.log("Calling in stages:", addOneAndTwo(3));
a and returns another function.b; closes over a.c and finally returns a + b + c.f(1)(2)(3) — each call returns the next function until the last.Writing a Generic curry() Helper
What this code does: Builds a reusable curry() helper that works on any function using fn.length.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log("All at once:", curriedMultiply(2, 3, 4));
console.log("One at a time:", curriedMultiply(2)(3)(4));
console.log("Mixed:", curriedMultiply(2, 3)(4));
fn.length is the original arity. When we have that many, call fn.fn.length.Currying Enables Partial Application
What this code does: Shows how currying lets you lock in some arguments early to create specialized functions.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
function formatMessage(level, prefix, message) {
return "[" + level + "] " + prefix + ": " + message;
}
const curriedFormat = curry(formatMessage);
// Lock in "level" and "prefix"
const logError = curriedFormat("ERROR", "App");
const logWarning = curriedFormat("WARNING", "App");
console.log(logError("Failed to connect to database"));
console.log(logWarning("Deprecated function used"));
console.log(logError("Timeout while fetching data"));
Currying vs Plain Partial Application
What this code does: Contrasts true currying with simple partial application using bind().
function volume(length, width, height) {
return length * width * height;
}
// Partial application with bind()
const baseVolume = volume.bind(null, 10);
console.log("Partial application:", baseVolume(5, 2));
// Currying
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
const curriedVolume = curry(volume);
console.log("Curried, one at a time:", curriedVolume(10)(5)(2));
bind(null, 10) fixes the first arg; returns a 2-arg function.(10)(5)(2) or any grouping.Practical Use Case: Building Reusable Utilities
What this code does: Uses currying to create specialized filter and map helpers that are easy to reuse.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
const filter = curry((predicate, array) => array.filter(predicate));
const map = curry((transform, array) => array.map(transform));
const greaterThan = curry((min, n) => n > min);
const multiplyBy = curry((factor, n) => n * factor);
const numbers = [1, 5, 10, 15, 20, 25];
const keepAboveTen = filter(greaterThan(10));
const doubleAll = map(multiplyBy(2));
console.log("Above ten:", keepAboveTen(numbers));
console.log("Doubled:", doubleAll(numbers));
console.log("Both combined:", doubleAll(keepAboveTen(numbers)));
Currying vs Regular Functions
| Feature | Regular Function | Curried Function |
|---|---|---|
| How arguments are passed | All at once | One at a time (or grouped) |
| Return value per call | The final result | Another function until fully applied |
| Supports partial application? | Not natively | Yes, inherently |
| Good for | Simple one-shot calls | Building reusable specialized functions |
Common Mistakes
- Currying functions that use default or rest parameters (fn.length is wrong)
- Over-currying simple functions that don’t need it
- Logging intermediate results and seeing “[Function]”
- Losing
thiswhen currying methods - Thinking currying improves performance (it doesn’t)
Best Practices
- Use currying when you repeatedly write similar functions that differ by only a few arguments
- Keep curried functions pure (no side effects)
- Prefer a generic, well-tested curry helper
- Order parameters from most general to most specific
- Don’t force currying on every function
Quick Summary
- Currying turns a multi-argument function into a chain of single-argument functions
- A generic
curry()helper usesfn.lengthto know when to call the original function - It enables partial application — locking in arguments to create specialized functions
- Best for building small, reusable, composable utilities
Try It Yourself
What this code does: Curries a URL builder and creates a specialized helper for one protocol and domain.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
function buildUrl(protocol, domain, path) {
return protocol + "://" + domain + "/" + path;
}
const curriedBuildUrl = curry(buildUrl);
// Lock in protocol and domain
const onMySite = curriedBuildUrl("https", "example.com");
console.log(onMySite("home"));
console.log(onMySite("about"));
console.log(curriedBuildUrl("http", "test.local", "debug"));
Tip: If you keep writing near-identical functions that only differ by their first argument or two, a curried version will usually let you build them all from one general function.
🧪 Test Your JavaScript Code
Edit the code on the left and click “Run Code” to see the console output on the right.
Click “Run Code” to see the output here.
Code Explanation
The complete example stays visible. Use Previous and Next to move through its logical blocks.
Select Next to begin exploring the code.