Explain currying and partial application in JavaScript
Advertisement
🧩 Scenario
Architecture Walkthrough
Currying: Chains of Unary Functions
Currying transforms a function that takes N arguments into a chain of N functions that each take one argument. Calling a curried function with one argument does not produce a result; it produces a new function waiting for the next argument. Only when all required arguments are supplied does the original function execute and return the final value.
The core use case is creating specialized variants of a general function by fixing arguments progressively. Each intermediate function in the chain is a fully reusable, composable unit. This makes curried functions easy to combine in pipelines where each step applies one transformation.
Partial Application: Pre-Filling Arguments
Partial application is different from currying: it pre-fills one or more arguments of a function and returns a new function that accepts the remaining arguments all at once. The resulting function does not have to be called one argument at a time. JavaScript's built-in bind is a partial application mechanism: fn.bind(context, arg1, arg2) returns a new function where arg1 and arg2 are already fixed.
Partial application is generally more pragmatic than full currying for typical JavaScript code. You rarely need a chain of six single-argument calls. More often, you want to fix a configuration argument (locale, base URL, log level) and pass around a function that accepts the remaining data argument.
The fn.length Limitation and Arity
A generic curry utility uses fn.length to determine when enough arguments have been collected to invoke the original function. fn.length returns the number of parameters declared in the function signature, not including rest parameters or parameters with defaults. A variadic function written as function sum(...nums) has fn.length === 0, so a naive curry utility cannot automatically determine when to invoke it.
The workaround is to pass the expected arity explicitly to the curry utility: curry(fn, 3). Libraries like Ramda handle this by requiring you to specify arity separately for variadic functions.
Key Code Explained
// Manual curry: two-argument example
function multiply(a) {
return function (b) {
return a * b;
};
}
const double = multiply(2); // b => 2 * b
const triple = multiply(3);
double(5); // 10
triple(5); // 15
// Generic curry utility
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 add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6 — one arg at a time
add(1, 2)(3); // 6 — batching works too
add(1)(2, 3); // 6 — any split
// Partial application with bind
function formatDate(locale, timezone, date) {
return new Intl.DateTimeFormat(locale, { timeZone: timezone }).format(date);
}
const formatIST = formatDate.bind(null, 'en-IN', 'Asia/Kolkata');
formatIST(new Date()); // formatted in IST, only the date changes per call
// Practical API client example using curry
const request = curry(async (method, baseUrl, endpoint, body) => {
const res = await fetch(`${baseUrl}${endpoint}`, {
method,
body: body ? JSON.stringify(body) : undefined,
headers: { 'Content-Type': 'application/json' },
});
return res.json();
});
const getFromApi = request('GET')('https://api.example.com');
const postToApi = request('POST')('https://api.example.com');
// Only the endpoint (and body for POST) changes per call
const user = await getFromApi('/users/42');
await postToApi('/submissions', { challengeId: 'io-001' });
The API client example is the most instructive real-world use. getFromApi and postToApi are specialized functions derived from one general request function. The method and base URL are fixed once; callers only provide what varies per request.
Tradeoffs
| Approach | Pro | Con |
|---|---|---|
| Manual currying | Full control, no utility dependency | Verbose for functions with many arguments |
| Generic curry utility | DRY, flexible calling style | Breaks for variadic functions, adds indirection |
| Partial application with bind | Native, zero dependencies | Positional, only fills leading arguments |
| Closure-based partial application | Flexible, named parameters | More boilerplate than bind for simple cases |
What Interviewers Actually Check
- Whether you can clearly distinguish currying from partial application
- Whether you can write a generic curry utility and explain how it uses
fn.length - Whether you know
fn.lengthis 0 for variadic functions and can describe the consequence - Whether you can name a real use case where either pattern eliminates code duplication
- Whether you understand that
bindis a built-in partial application tool
Follow-Up Questions
- How would you implement
composeorpipeutilities that work well with curried functions? - How does Ramda's approach to currying differ from a simple recursive curry utility?
- If a curried function is called with too many arguments all at once, what does a well-implemented curry utility do?
- How does TypeScript handle the return type inference of a generic curry utility?
- A teammate uses
_.curryfrom Lodash on every utility function by default. What questions would you ask before merging the PR?
Common Candidate Mistakes
- Using "currying" and "partial application" as synonyms when they describe different transformation patterns
- Writing a curry utility and not knowing it fails for functions that use rest parameters because
fn.lengthreturns 0 - Thinking curried functions must be called one argument at a time when a proper implementation supports batching
- Not knowing that
Function.prototype.bindis a built-in partial application mechanism - Applying currying to functions that are always called with all arguments, adding complexity with no benefit
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you explain the difference between currying and partial application without confusing them?
- Can you write a manually curried two-argument function and a generic curry utility?
- Can you explain why
fn.lengthbreaks for variadic functions and how to work around it? - Can you use
Function.prototype.bindfor partial application with a concrete example? - Can you name a real use case where currying or partial application reduces code duplication?
Summary
Currying transforms a function of N arguments into a chain of N unary functions. Each call returns a new function that waits for the next argument until all have been provided. Partial application is a looser pattern: you pre-fill one or more arguments and return a function that accepts the remainder, all at once.
Both patterns produce specialized functions from general ones, reducing repetition when the same argument values are used across many call sites. Function.prototype.bind provides partial application natively. Generic curry utilities can handle any function but break when rest parameters are used because fn.length reports 0 for variadic signatures.
The practical value of these patterns is most visible in API clients, formatters, and validators, where configuration arguments are fixed at setup time and data arguments vary per call. For most everyday JavaScript, partial application with closures or bind is sufficient; full currying is more valuable in functional programming pipelines where point-free composition is the goal.
Is currying the same as partial application?
Not exactly. Currying transforms a function of N args into N functions of 1 arg each. Partial application pre-fills some arguments and returns a function that accepts the rest.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement