JavaScriptMedium

Explain currying in JavaScript

01

The Short Answer

Currying is a technique where a function that takes multiple arguments is transformed into a sequence of functions, each taking a single argument. Instead of calling add(2, 3), you call add(2)(3). Each call returns a new function that 'remembers' the previous arguments via closure, until all arguments are provided and the final result is computed. Currying enables partial application, function composition, and creating specialized versions of general functions.

02

Basic Currying

The simplest way to understand currying is to compare a regular multi-argument function with its curried version. The curried version takes one argument at a time, returning a new function at each step. Each returned function closes over the previously provided arguments, building up the full argument list until the function can produce a result.

basic-currying.tstypescript
// Regular function — takes all arguments at once
function add(a: number, b: number): number {
  return a + b;
}
add(2, 3); // 5

// Curried version — takes one argument at a time
function curriedAdd(a: number) {
  return function (b: number): number {
    return a + b;
  };
}
curriedAdd(2)(3); // 5

// Arrow function syntax — more concise
const curriedAdd2 = (a: number) => (b: number) => a + b;
curriedAdd2(2)(3); // 5

// Three arguments curried
const multiply = (a: number) => (b: number) => (c: number) => a * b * c;
multiply(2)(3)(4); // 24

// Each call returns a new function
const double = curriedAdd2(0); // Returns (b) => 0 + b... wait, that's identity
const addFive = curriedAdd2(5); // Returns (b) => 5 + b
addFive(3);  // 8
addFive(10); // 15
// addFive is a specialized version of add — this is the power of currying

The key insight is that curriedAdd(2) doesn't compute anything yet — it returns a function that's waiting for the second argument. The value 2 is captured in a closure. When you finally call that returned function with 3, both values are available and the computation happens.

03

Why Currying is Useful

Currying shines when you need to create specialized functions from general ones. Instead of repeating the same arguments over and over, you 'bake in' the common arguments once and reuse the specialized function. This is especially powerful with higher-order functions like map, filter, and event handlers.

practical-currying.tstypescript
// Creating specialized functions from a general one
const formatCurrency = (symbol: string) => (amount: number): string => {
  return `${symbol}${amount.toFixed(2)}`;
};

const formatUSD = formatCurrency('$');
const formatEUR = formatCurrency('€');
const formatINR = formatCurrency('₹');

formatUSD(42.5);  // "$42.50"
formatEUR(42.5);  // "€42.50"
formatINR(1500);  // "₹1500.00"

// Currying with array methods — clean, reusable predicates
const hasMinLength = (min: number) => (str: string): boolean => str.length >= min;
const isOlderThan = (age: number) => (user: { age: number }): boolean => user.age > age;

const passwords = ['ab', 'secure123', 'hi', 'longpassword'];
passwords.filter(hasMinLength(8)); // ['secure123', 'longpassword']

const users = [{ age: 15 }, { age: 22 }, { age: 30 }];
users.filter(isOlderThan(18)); // [{ age: 22 }, { age: 30 }]

// Event handler factories
const handleFieldChange = (fieldName: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
  setForm(prev => ({ ...prev, [fieldName]: event.target.value }));
};

// In JSX — no need for inline arrow functions
// <input onChange={handleFieldChange('email')} />
// <input onChange={handleFieldChange('name')} />
04

Generic Curry Utility

You can write a generic curry function that transforms any multi-argument function into its curried form automatically. It collects arguments one at a time (or in groups) and calls the original function once enough arguments are accumulated. This is how libraries like Ramda and Lodash implement currying.

curry-utility.tstypescript
// Generic curry utility
function curry<T extends (...args: any[]) => any>(fn: T) {
  return function curried(...args: any[]): any {
    // If we have enough arguments, call the original function
    if (args.length >= fn.length) {
      return fn(...args);
    }
    // Otherwise, return a function that collects more arguments
    return (...moreArgs: any[]) => curried(...args, ...moreArgs);
  };
}

// Usage — transform any function into curried form
const add = (a: number, b: number, c: number) => a + b + c;
const curriedAdd = curry(add);

curriedAdd(1)(2)(3);    // 6
curriedAdd(1, 2)(3);    // 6 — can pass multiple args at once
curriedAdd(1)(2, 3);    // 6
curriedAdd(1, 2, 3);    // 6 — works like the original too

// Real-world: curried API request builder
const request = (method: string, baseUrl: string, path: string) => {
  return fetch(`${baseUrl}${path}`, { method });
};

const curriedRequest = curry(request);
const get = curriedRequest('GET');
const getFromApi = get('https://api.example.com');

// Now making requests is clean:
getFromApi('/users');    // GET https://api.example.com/users
getFromApi('/posts');    // GET https://api.example.com/posts

The curry utility checks fn.length (the number of declared parameters) to know when enough arguments have been collected. This is why it works with any function — it adapts to the arity automatically. Note that rest parameters (...args) don't count toward fn.length, so curry utilities don't work well with variadic functions.

05

Function Composition with Currying

Currying enables elegant function composition — building complex transformations by chaining simple, single-purpose functions. When every function takes one argument and returns one value, they become like LEGO blocks that snap together. This is the foundation of functional programming pipelines.

composition.tstypescript
// Composable, curried utility functions
const map = <T, U>(fn: (item: T) => U) => (arr: T[]): U[] => arr.map(fn);
const filter = <T>(predicate: (item: T) => boolean) => (arr: T[]): T[] => arr.filter(predicate);
const prop = <T, K extends keyof T>(key: K) => (obj: T): T[K] => obj[key];
const gt = (threshold: number) => (value: number): boolean => value > threshold;

// Compose: right-to-left function application
const compose = <T>(...fns: Array<(arg: any) => any>) =>
  (input: T) => fns.reduceRight((acc, fn) => fn(acc), input as any);

// Build a data pipeline from small, reusable pieces
type User = { name: string; age: number; active: boolean };

const getActiveAdultNames = compose<User[]>(
  map((user: User) => user.name),       // 3. Extract names
  filter((user: User) => user.age >= 18), // 2. Keep adults
  filter((user: User) => user.active)     // 1. Keep active users
);

const users: User[] = [
  { name: 'Alice', age: 25, active: true },
  { name: 'Bob', age: 16, active: true },
  { name: 'Charlie', age: 30, active: false },
];

getActiveAdultNames(users); // ['Alice']
06

Common Interview Follow-ups

Interviewers often ask you to implement a curried function that works with any number of calls and returns the sum when called with no arguments, or when converted to a primitive. This tests your understanding of closures, recursion, and JavaScript's type coercion.

infinite-curry.tstypescript
// Classic interview question: sum(1)(2)(3)() === 6
function sum(a: number) {
  let total = a;

  function inner(b?: number): any {
    if (b === undefined) return total; // No argument = return result
    total += b;
    return inner; // Return itself for chaining
  }

  return inner;
}

sum(1)(2)(3)();     // 6
sum(5)(10)(15)();   // 30

// Alternative: use valueOf/toString for implicit conversion
function add(a: number) {
  function inner(b: number) {
    return add(a + b);
  }
  // When JS needs a primitive value, it calls valueOf
  inner.valueOf = () => a;
  inner.toString = () => String(a);
  return inner;
}

// Works with implicit type coercion:
+add(1)(2)(3);        // 6 (unary + triggers valueOf)
add(1)(2)(3) == 6;    // true (== triggers valueOf)
`${add(1)(2)(3)}`;    // "6" (template literal triggers toString)
07

Why Interviewers Ask This

Currying tests multiple JavaScript fundamentals at once: closures (how returned functions remember outer variables), higher-order functions (functions returning functions), function composition patterns, and practical API design. Interviewers want to see that you understand the mechanics (closures + returning functions), can identify real use cases (specialized functions, clean predicates for array methods), know how to implement a generic curry utility, and understand the connection to functional programming concepts.

Quick Revision Cheat Sheet

What currying is: Transforming f(a, b, c) into f(a)(b)(c) — one argument at a time

How it works: Each call returns a new function that closes over previous arguments

Key benefit: Create specialized functions from general ones (formatUSD from formatCurrency)

With arrays: Curried predicates are clean with .map/.filter: filter(isOlderThan(18))

Curry utility: Collects args until fn.length is reached, then calls original function

Composition: Curried functions compose naturally — each takes one input, returns one output