Explain currying in JavaScript
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.
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.
// 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.
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.
// 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')} />
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.
// 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.
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.
// 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']
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.
// 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)
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