DNA⚡ JavaScriptCurrying & Partial Application
ðŸĶ–DinosaurJavaScriptFunctional ProgrammingPatterns

Currying & Partial Application

Currying transforms a multi-argument function into a chain of single-argument functions. It's the backbone of composition, reusable utilities, and functional middleware.

Currying & Partial Application

Currying isn't academic — it's how you build composable utility layers. Every time you write const add5 = add(5) or const withAuth = withHeaders({ Authorization: token }), you're using partial application powered by closures.

Currying vs Partial Application

These are related but distinct concepts:

Currying:             f(a, b, c) → f(a)(b)(c)
Partial Application:  f(a, b, c) → g(c)  where a, b are pre-filled

Currying transforms a function so each argument is consumed one at a time. Partial application pre-fills some arguments and returns a function awaiting the rest.

Manual Currying

function multiply(a) {
  return function(b) {
    return a * b;
  };
}
 
const double = multiply(2);
const triple = multiply(3);
 
double(5);  // 10
triple(5);  // 15

With arrow functions, curried factories become concise:

const multiply = a => b => a * b;
const add = a => b => a + b;
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
 
const transform = pipe(
  add(1),
  multiply(2),
  multiply(3),
);
 
transform(4); // ((4 + 1) * 2) * 3 = 30

Generic Curry Implementation

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

Edge Cases in the Implementation

  • fn.length counts only parameters before the first default or rest parameter
  • Variadic functions (...args) have length of 0 — curry breaks on them
  • this binding must be preserved for method currying
function curryWithArity(fn, arity = fn.length) {
  return function curried(...args) {
    if (args.length >= arity) {
      return fn.apply(this, args);
    }
    return function(...nextArgs) {
      return curried.apply(this, [...args, ...nextArgs]);
    };
  };
}
 
const sum = curryWithArity((...nums) => nums.reduce((a, b) => a + b, 0), 3);
sum(1)(2)(3); // 6

Partial Application with bind

JavaScript has built-in partial application via Function.prototype.bind:

function log(level, timestamp, message) {
  console.log(`[${level}] ${timestamp}: ${message}`);
}
 
const warn = log.bind(null, 'WARN');
const warnNow = warn.bind(null, Date.now());
 
warn(Date.now(), 'Disk space low');
warnNow('Disk space low');

bind pre-fills from left to right. For right-to-left or arbitrary position filling, you need a custom partial:

const PLACEHOLDER = Symbol('placeholder');
 
function partial(fn, ...presetArgs) {
  return function(...laterArgs) {
    const args = presetArgs.map(arg =>
      arg === PLACEHOLDER ? laterArgs.shift() : arg
    );
    return fn(...args, ...laterArgs);
  };
}
 
const div = (a, b) => a / b;
const halve = partial(div, PLACEHOLDER, 2);
halve(10); // 5

Real-World Patterns

API Client Factory

const createFetcher = (baseUrl: string) => (path: string) =>
  (options?: RequestInit) => fetch(`${baseUrl}${path}`, options);
 
const api = createFetcher('https://api.example.com');
const getUsers = api('/users');
const getPosts = api('/posts');
 
await getUsers();
await getUsers({ headers: { Authorization: `Bearer ${token}` } });

Event Handler Factories

const handleFieldChange = (field: string) => (e: ChangeEvent<HTMLInputElement>) => {
  setForm(prev => ({ ...prev, [field]: e.target.value }));
};
 
<input onChange={handleFieldChange('email')} />
<input onChange={handleFieldChange('name')} />

Middleware Composition

const withLogging = next => action => {
  console.log('dispatching', action);
  const result = next(action);
  console.log('next state', store.getState());
  return result;
};
 
const withCrashReporting = next => action => {
  try {
    return next(action);
  } catch (err) {
    reportError(err);
    throw err;
  }
};

Performance Considerations

Currying creates intermediate closures. In hot paths, a single function with all arguments is faster:

// Hot path — avoid currying
for (let i = 0; i < 1_000_000; i++) {
  result += multiply(2)(i); // creates closure per iteration
}
 
// Better — use the pre-applied version
const double = multiply(2);
for (let i = 0; i < 1_000_000; i++) {
  result += double(i); // reuses single closure
}

Interview Signal

Senior candidates demonstrate:

  1. Clarity on curry vs partial — They're related but not the same; curry is strict one-arg-at-a-time, partial is flexible pre-filling
  2. Implementation depth — Handling fn.length, variadic functions, this binding, placeholder support
  3. Practical application — API factories, event handlers, middleware, composition pipelines
  4. Trade-off awareness — Closure overhead in hot paths, readability vs abstraction balance