Back to JavaScript
JavaScript
easy
mid

What are common interview questions that combine closures and currying?

Closures: inner functions remember their enclosing scope. Currying turns `f(a,b,c)` into `f(a)(b)(c)` using nested closures. Combined patterns interviewers love: counter factories, once/memoize, partial application, debounce/throttle, infinitely-curryable functions like `sum(1)(2)(3)... .valueOf()`.

4 min read·~20 min to think through

Closures + currying is one of the most common interview pairings. Both rely on the same mechanism: a function remembering variables from the scope it was created in.

Closure refresher

js
function makeCounter() {
  let count = 0;
  return () => ++count;
}
const c = makeCounter();
c(); c(); c();      // 1, 2, 3

count survives because the returned function holds a reference to its scope.

Currying — function of N args → chain of unary functions

js
const curry = (fn) => {
  return function curried(...args) {
    if (args.length >= fn.length) return fn.apply(this, args);
    return (...more) => curried.apply(this, [...args, ...more]);
  };
};
const add = (a, b, c) => a + b + c;
const cAdd = curry(add);
cAdd(1)(2)(3);     // 6
cAdd(1, 2)(3);     // 6
cAdd(1)(2, 3);     // 6

Patterns interviewers ask

1. Counter factory (closures)

js
function makeCounter(start = 0) {
  return {
    inc: () => ++start,
    dec: () => --start,
    value: () => start,
  };
}

2. once (closure)

js
function once(fn) {
  let called = false, result;
  return (...args) => (called ? result : (called = true, result = fn(...args)));
}

3. memoize (closure)

js
function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (!cache.has(key)) cache.set(key, fn(...args));
    return cache.get(key);
  };
}

4. Partial application (currying lite)

js
const partial = (fn, ...preset) => (...rest) => fn(...preset, ...rest);
const greet = (greeting, name) => `${greeting}, ${name}!`;
const hi = partial(greet, "Hi");
hi("Anya");        // "Hi, Anya!"

5. Infinitely curryable sum

js
function sum(a) {
  let total = a;
  const inner = (b) => { total += b; return inner; };
  inner.valueOf = () => total;
  return inner;
}
sum(1)(2)(3) + 0;   // 6 — coerces via valueOf

6. Debounce/throttle (closure-based)

The lastCall, timer, and lastArgs live in the closure (see [[how-to-implement-throttling]]).

7. Module pattern (pre-ES6)

js
const counter = (function() {
  let n = 0;
  return { inc: () => ++n, get: () => n };
})();

Closure gotchas

Loop variable capture (var)

js
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);    // 3, 3, 3 — shared var
}

Fix: let (block scope), or IIFE wrapper for the var case.

Memory leaks via captured DOM

A closure that captures a large object (or DOM node) keeps it alive — be intentional about what you close over.

Interview framing

"Closures let inner functions remember the scope they were created in — that's the foundation of factories, once, memoize, debouncing, and the module pattern. Currying applies that to transform f(a,b,c) into a chain of unary calls — useful for partial application and point-free composition. The classic combinations interviewers love are: a counter factory, once/memoize, an infinitely-curryable sum via valueOf, partial application, and the var-in-loop closure puzzle. The underlying mechanism is the same in all of them — a function holding a reference to its lexical environment."

Follow-up questions

  • Implement curry that supports placeholders (curry(add)(_, 2)(1)).
  • Why does the var-in-loop closure print 3,3,3?
  • Implement memoize with a custom cache key.
  • How does the infinitely-curryable sum trick work?

Common mistakes

  • Using JSON.stringify for memoize keys with non-serializable args (functions, circular refs).
  • Forgetting that var is function-scoped — closure captures the same binding.
  • Currying without supporting variadic last-call (a, b, c) | (a)(b)(c) | (a, b)(c).
  • Memory leaks by closing over large objects you no longer need.

Performance considerations

  • Closures are cheap; the cost is in what they capture. Memoize speeds repeat calls at the cost of memory; cap the cache for hot paths.

Edge cases

  • Currying a function with default parameters — fn.length excludes them.
  • Currying a function that uses this — bind appropriately.
  • Memoize cache growing unbounded — consider LRU.

Real-world examples

  • Lodash curry/partial/once/memoize.
  • React hooks themselves are closure-based (each render closes over its values).

Senior engineer discussion

Seniors recognize closures as the underlying mechanism behind a half-dozen common patterns and use them deliberately — capping memo caches, picking the right key strategy, and avoiding memory leaks by being intentional about what they close over.

Related questions