The hardest curry variant: transform f(a, b, c, d) so it accepts any number of arguments at each call — f(a)(b, c)(d) — and invokes the original when enough args have been collected. It's curry without the one-arg-at-a-time restriction.
Related deep-dive: Functions #2 — Currying & Partial Application
What is Variadic curry()?
Variadic curry(fn) is the generalization of the fixed-arity curry from Interview #11. Instead of requiring exactly one argument per call — curried(a)(b)(c) — the variadic version accepts any number of arguments at each call: curried(a, b)(c), curried(a)(b, c), or even curried(a, b, c). It invokes fn as soon as the total accumulated arguments reaches or exceeds fn.length.
The core mechanism is the same as fixed-arity curry: recursive argument accumulation with fn.length as the termination condition. The difference is that each call collects ...args (any number) rather than a single argument, and all collected args are spread onto the accumulator.
The termination condition totalArgs.length >= fn.length is what makes this "variadic" — you can provide arguments in any-sized batches. Once the total meets or exceeds the function's declared parameter count, the function executes. Excess arguments beyond fn.length are still passed through (matching native behavior).
Real-world use cases:
- Flexible partial application —
const greetHello = curry(greet)("Hello")then latergreetHello("Alice")orgreetHello("Bob") - Configuration pipelines — pre-configure a function with config in one call, then pass the data in another:
const configured = curry(process)(config); configured(data) - Event handlers —
const handler = curry(handleEvent)(eventType)(metadata)then attachhandler(payload)to the actual event
The variadic version is the one that matches how Lodash's _.curry works and is the version most useful in practice. The fixed-arity version is primarily an interview stepping stone.
The Problem
"Implement curry(fn) where the curried function can be called with any number of arguments at each step. It should invoke fn as soon as the total accumulated arguments reaches or exceeds fn.length."
Thought Process
The pattern from Interview #11 (fixed-arity curry) only allows one argument at a time. Here we need to:
- Accumulate args across calls
- Check after each call whether we have enough
- If yes, invoke
fnwith the accumulated args - If no, return a new function that continues accumulating
The key difference from #11: instead of returning (nextArg) => ..., we return (...nextArgs) => ... — allowing any number of arguments per call.
Step 1 — Base Variadic Curry
Loading editor...
Step 2 — Handling Extra Arguments
The interviewer asks: "What if you pass more arguments than fn expects?"
Loading editor...
Some implementations pass all args to fn and let the function decide. Others slice to fn.length. State your choice and why.
Step 3 — Preserving this Context
Loading editor...
Step 4 — Functions with Rest Parameters or Defaults
The interviewer: "What about fn.length for functions with rest params?"
Loading editor...
Key insight: Functions with rest params (...args) have fn.length === 0. Similarly, functions with default params only count parameters before the first default. If the interviewer gives you such a function, ask: "Should I accept an explicit arity?"
Step 5 — Infinite Currying with Terminator
A related variant: infinite currying where the function is called with no arguments to terminate:
Loading editor...
Note: this variant accumulates args in a closure across all call chains (the args array is shared). For a fresh accumulator each time, re-create the curried function.
Edge Cases
fn.length = 0 (no-arg function): curry(() => 'hello')() — the first call has args.length >= 0, so it invokes immediately. Correct.
Passing more args than expected: Either slice to fn.length or pass all. State your choice.
Calling with no arguments when arity isn't met: Return a new function that waits for args. Don't invoke fn prematurely.
Full Solution
Loading editor...
What Interviewers Are Testing
- Accumulation pattern — gathering arguments across multiple calls
- Arity detection — using
fn.lengthto know when to invoke - Rest parameter handling — understanding that
fn.lengthdoesn't count rest params - Explicit arity override — letting the caller specify arity when
fn.lengthis unreliable thisforwarding — preserving context through all call levels
Complexity
| Time | Space | |
|---|---|---|
| create curried | O(1) | O(1) |
| each call | O(1) — spread + concat | O(N) — accumulated args |
Interview Tips
- Start from the fixed-arity curry — "This is the same pattern as fixed-arity curry, but
(arg)becomes(...args). That's the only change at the base level." - Ask about
fn.lengthedge cases early — "How should I handle functions with rest parameters or default values? Should I accept an explicit arity parameter?" - Show infinite curry as a variant — "There's also infinite curry where you call with
()to terminate. That uses a shared accumulator rather than arity-checking." - Mention
thisforwarding — use.apply(this, ...)even if the test cases don't usethis. It shows real-world awareness.