Engrain by ByteRush

JS.07.02 Mid-level· 10 minCore — asked often

Currying and partial application: fixing some arguments now, the rest later

Currying turns f(a, b, c) into f(a)(b)(c); partial application fixes some arguments up front. Learn both, fn.length and its limits, and write curry.

Comes up in: Coding

Learn#

Zara writes logEvent(app, level, message) and calls it 200 times as logEvent("checkout", "info", …). She wants checkoutInfo(message) without writing a new wrapper for every app and level.

Two tools do that. Partial application fixes some arguments now and returns a function for the rest: logEvent.bind(null, "checkout", "info"). Currying goes further: it turns a function of many arguments into a chain of calls that each take some arguments, until all are there — log("checkout")("info")("paid").

A function’s arity is how many arguments it expects. Currying needs a fixed arity to know when to stop.

A generic curry reads it from fn.length — but fn.length counts only parameters before the first default or rest parameter.

const curry = (fn) => function curried(...args) {
  return args.length >= fn.length ? fn.apply(this, args) : (...more) => curried.apply(this, [...args, ...more]);
};
const price = (rate, qty, unit) => rate * qty * unit;
const c = curry(price);
console.log(c(2)(3)(4), c(2, 3)(4), c(2)(3, 4));
const double = c(2);
console.log(double(5)(10));
console.log(((a, b = 2) => 0).length, ((...xs) => 0).length);

Output (verified)

24 24 24
100
1 0
curry collecting argumentsStep 1 of 4

collected

    empty

    needed

    • 3

    c = curry(price). price needs 3 arguments (price.length is 3).

    Read the steps as text
    1. c = curry(price). price needs 3 arguments (price.length is 3).
    2. c(2): only 1 argument so far. curry returns a new function that remembers [2].
    3. (3): now [2, 3]. Still not enough, so another function comes back.
    4. (4): [2, 3, 4] is enough. curry calls price(2, 3, 4) and returns 24.

    Predict#

    Predict first

    const f = curry((a, b = 10) => a + b); What is f(1)?

    Interview angle#

    Say it like this — about 30 seconds

    “Currying turns f(a, b, c) into a chain of calls. Each call takes some arguments, and the original runs once it has enough. Partial application just fixes some arguments up front, like bind does. To implement curry, I return a function that collects arguments in a closure; when the count reaches fn.length it calls the function, otherwise it returns another collector. fn.length ignores default and rest parameters. So a function with any number of arguments needs a set count or an end signal.”

    Stays on your device. Nothing is uploaded.
    Follow-up Implement sum(1)(2)(3)() that returns 6.
    Return a function that, when called with an argument, adds it and returns a new function; when called with no argument, returns the total.
    Follow-up What is placeholder currying?
    Libraries like Ramda let you skip positions with a placeholder: f(_, 2)(1) fills the second argument first. The collector fills placeholders before appending new arguments.
    Follow-up When is currying not worth it?
    In most app code, a small arrow — (msg) => logEvent("checkout", "info", msg) — is clearer to read and debug than a curried chain.

    Practice#

    Timed, like the real round

    Implement curry(fn) for any grouping of arguments, and the classic sum(1)(2)(3)() puzzle.

    Done when

    • curry(add3) gives 6 for (1)(2)(3), (1, 2)(3) and (1)(2, 3), and partial results are reusable.
    • sum(1)(2)(3)() is 6, and sum() is 0.
    Your solution
    Ctrl/⌘ + Enter

    Remember#

    Try to answer each card before you open it. Then add them to your reviews.

    What does currying do?

    Turns f(a, b, c) into calls that take arguments a few at a time, running f once all are collected.

    How does partial application differ from currying?

    It fixes some arguments once and returns a function for the rest.

    What does fn.length count?

    Parameters before the first default or rest parameter.

    Write curry(fn) from memory.

    Write it from memory, then run it against the tests in Practice.

    Go deeper

    Four layers. Stop at the one your interview needs; each ends with a quick check.

    The core ideaEvery round

    Partial application: fix some arguments now. Currying: keep accepting arguments until there are enough. Both rely on closures to remember what was given.

    Check yourself: Is bind(null, x) currying or partial application?

    Partial application.

    How it works underneathSenior rounds

    Each returned function keeps the arguments so far in its closure. Calling it builds a new array, so you can reuse partial results safely. fn.length is the function’s expected argument count as defined by its parameter list Source: MDN: Function.length.

    Check yourself: Where does curry store the arguments collected so far?

    In the closure of each returned function (the args array).

    Edge cases and trapsWhere people slip

    If the collector pushed into one shared array, const d = c(2); d(3)(4); d(5)(6) would mix the calls. Always build a new array. Calling with zero arguments, c(), adds nothing and returns another collector.

    Check yourself: Why should a curried collector copy the args array instead of pushing to it?

    Pushing would leak arguments between different branches of reuse.

    In productionStaff-level answers

    Ramda and lodash/fp curry every function, with the data last, so they join well with pipe. In TypeScript, typing a general curry is hard. Most teams use small arrow wrappers or bind instead Source: Ramda: curry.

    Check yourself: Name a library that curries all its functions.

    Ramda (also lodash/fp).

    Finished the five steps? Mark it, and its cards join your reviews.

    Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

    Interview questions this answers

    • What is currying? Implement curry.