What is Currying?
Imagine a vending machine that needs two coins to give you a drink. You don’t have to insert both coins at once, you can insert one coin now, and the other later. The machine will remember the first coin and wait for the second.
That’s exactly how currying works in JavaScript!
Currying is a functional programming technique where a function that takes multiple arguments is divided into a series of functions that each take one argument.
Instead of calling a function with all arguments at once, you call it with one argument at a time, and each call returns a new function that expects the next argument.
Example:
Imagine you have a function that takes multiple arguments:
function add(a, b) {
return a + b;
}
Currying transforms this into a series of functions, each taking one argument:
function curriedAdd(a) {
return function (b) {
return a + b;
};
}
curriedAdd(2)(3); // Output: 5
Here,
- First, you call
curriedAdd(2)
. This returns a new function that remembersa = 2
. - Then, you call that returned function with
3
. - It adds
2 + 3
and gives you5
. - So instead of calling
add(2, 3)
, you callcurriedAdd(2)(3)
.
Why Use Currying?
- Reusability: You can make custom functions by giving some values in advance. These functions remember the values you gave and only need the remaining ones later.
- Partial Application: You can pass arguments one at a time instead of all at once.
- Function Composition: Makes it easier to compose small functions.
- Code Organization: Makes the code more modular and maintainable.
- Flexibility: You can build the final function step by step, customizing as you go.
- Readability: Clear separation of function parameters.
Basic Example of Currying
// Regular function (not curried)
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // Output: 6
// Curried version
function curriedAdd(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // Output: 6
Here,
- Regular function
function add(a, b, c) {...}
:- Takes all arguments at once:
a
,b
, andc
. - Calculates and returns the result directly.
- It's simple to use, but you can't easily reuse part of it with different values.
- Takes all arguments at once:
- Curried function
function curriedAdd(a) {...}
:curriedAdd(1)
returns a function that remembersa = 1
.- That function is called with
2
remembersb = 2
. - That function is called with
3
now hasa + b + c
. - Returns
1 + 2 + 3 = 6
Understanding the Flow
Let's break down what happens step by step:
function curriedAdd(a) {
console.log("First function called with:", a);
return function (b) {
console.log("Second function called with:", b);
return function (c) {
console.log("Third function called with:", c);
return a + b + c;
};
};
}
const step1 = curriedAdd(1); // Returns a function
const step2 = step1(2); // Returns another function
const result = step2(3); // Returns the final result (6)
Modern ES6 Arrow Function Syntax
Arrow functions make currying more concise.
// Using arrow functions
const curriedAdd = (a) => (b) => (c) => a + b + c;
console.log(curriedAdd(1)(2)(3)); // Output: 6
Here,
- Each arrow
=>
creates a function that takes one argument and returns the next function. - The last function returns the sum of
a + b + c
. - It’s a shorter and cleaner way to write curried functions compared to the regular function syntax.
Creating a Generic Curry Function
You can create an utility function that converts any multi-argument function into a curried version.
This new function lets you give arguments one by one or a few at a time. It keeps waiting and collecting until it has all the arguments it needs, then it runs the original function.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
// If enough arguments are collected, call the original function
return fn.apply(this, args);
} else {
// Otherwise, return a new function to collect more arguments
return function (...args2) {
// Combine old and new arguments and call curried again
return curried.apply(this, args.concat(args2));
};
}
};
}
// Usage
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // Output: 6
console.log(curriedAdd(1, 2)(3)); // Output: 6
console.log(curriedAdd(1)(2, 3)); // Output: 6
console.log(curriedAdd(1, 2, 3)); // Output: 6
Here,
-
fn.length
is the number of arguments the original function expects. -
args
holds all arguments collected so far. -
If enough arguments are collected (
args.length >= fn.length
), it runsfn
, the original function with all the collected arguments. -
If not, it returns a function to collect more arguments and calls itself recursively.
-
When you call that new function, it adds new arguments to the old ones and repeats the process.
-
Example with add:
const add = (a, b, c) => a + b + c; const curriedAdd = curry(add);
-
You can call the curried function in many ways:
curriedAdd(1)(2)(3); // 6 curriedAdd(1, 2)(3); // 6 curriedAdd(1)(2, 3); // 6 curriedAdd(1, 2, 3); // 6
It collects arguments little by little until it has all three numbers, then adds them.
Why Is This Useful?
- You can call your function with any number of arguments at a time.
- It makes your functions more flexible and reusable.
Partial Application
One of the main benefits of currying is partial application, you can create specialized functions by providing some arguments.
const multiply = (x) => (y) => x * y;
// Create specialized functions that already know one number
const double = multiply(2); // always multiply by 2
const triple = multiply(3); // always multiply by 3
console.log(double(5)); // 10 (2 * 5)
console.log(triple(4)); // 12 (3 * 4)
// Or call it directly
console.log(multiply(2)(5)); // 10
Here,
const multiply = (x) => (y) => x * y;
- This is a curried function written with arrow functions.
- It takes one argument
x
and returns a new function. - The returned function takes another argument
y
and returns the productx * y
.
- Creating specialized functions,
const double = multiply(2);
andconst triple = multiply(3);
:- When you call
multiply(2)
, it returns a function that multiplies any number by2
. - This returned function is saved as
double
. - Similarly,
multiply(3)
returns a function that multiplies any number by3
, saved astriple
. - These are specialized versions of the
multiply
function, with one argument already fixed.
- When you call
- Using the specialized functions,
console.log(double(5));
andconsole.log(triple(4));
:- Calling
double(5)
is the same as callingmultiply(2)(5)
. - It multiplies
2 * 5
and returns10
. - Similarly,
triple(4)
returns3 * 4 = 12
.
- Calling
- Using the curried function directly,
console.log(multiply(2)(5));
:- You can also call the function all at once by passing one argument at a time.
- First call with
2
returns a function waiting for the next argument. - Second call with
5
does the multiplication.
Why is this useful?
- You can create functions tailored for specific tasks (like doubling or tripling).
- These specialized functions are easy to reuse.
- Currying with partial application help you write cleaner, more flexible code.
Practical Examples
Example 1: Greeting Function
const greet = (greeting) => (name) => `${greeting}, ${name}!`;
const sayHello = greet("Hello");
const sayGoodbye = greet("Goodbye");
console.log(sayHello("Alice")); // Output: "Hello, Alice!"
console.log(sayGoodbye("Bob")); // Output: "Goodbye, Bob!"
Here,
const greet = (greeting) => (name) =>
${greeting}, ${name}!;
:- This is a curried function using arrow syntax.
- It takes the first input greeting (like "Hello" or "Goodbye") and returns another function.
- The returned function takes the second input
name
(like "Alice" or "Bob"). - Finally, it returns a greeting message combining both inputs.
- Creating specialized functions,
const sayHello = greet("Hello");
andconst sayGoodbye = greet("Goodbye");
:sayHello
is now a function that remembersgreeting = "Hello"
.sayGoodbye
remembersgreeting = "Goodbye"
.
- Using the specialized functions:
sayHello("Alice")
adds "Alice" to "Hello" and returns "Hello, Alice!".sayGoodbye("Bob")
adds "Bob" to "Goodbye" and returns "Goodbye, Bob!".
Example 2: Filtering Arrays
const filter = (predicate) => (array) => array.filter(predicate);
const numbers = [1, 2, 3, 4, 5, 6];
// Create specialized filters
const filterEven = filter((x) => x % 2 === 0);
const filterGreaterThan3 = filter((x) => x > 3);
console.log(filterEven(numbers)); // Output: [2, 4, 6]
console.log(filterGreaterThan3(numbers)); // Output: [4, 5, 6]
Here,
const filter = (predicate) => (array) => array.filter(predicate);
:- This is a curried function.
- It takes a
predicate
function first, a test that returnstrue
orfalse
for each item. - It returns a new function that takes an array.
- This returned function uses JavaScript’s built-in
array.filter()
method with the givenpredicate
.
- Creating specialized filter functions:
filterEven
is a function that filters an array and keeps only even numbers.filterGreaterThan3
keeps numbers greater than 3.
- Using the specialized functions:
- When you call
filterEven(numbers)
, it returns only even numbers from the array. - When you call
filterGreaterThan3(numbers)
, it returns numbers greater than 3.
- When you call
Example 3: Event Handling
const addEventListener = (element) => (event) => (handler) => {
element.addEventListener(event, handler);
};
const button = document.getElementById("myButton");
const addClickListener = addEventListener(button)("click");
addClickListener(() => console.log("Button clicked!"));
addClickListener(() => console.log("Another click handler!"));
Here,
- The
addEventListener
function is a curried function with three steps:- First, you give the DOM element (
element
). - Then, you give the event type (like "click").
- Finally, you provide the event handler function.
- First, you give the DOM element (
- Creating a specialized function for your
button
:const addClickListener = addEventListener(button)("click");
- This remembers the
button
element and the "click" event. - Now
addClickListener
is a function that just needs thehandler
.
- This remembers the
- Adding multiple click handlers easily:
addClickListener(() => console.log("Button clicked!")); addClickListener(() => console.log("Another click handler!"));
- You can add many click event handlers without repeating the element and event type.
- This keeps your code clean and easy to reuse.
Example 4: Validation
const validate = (rule) => (value) => rule(value);
const isRequired = (value) => value != null && value !== "";
const isEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
const minLength = (min) => (value) => value.length >= min;
const validateRequired = validate(isRequired);
const validateEmail = validate(isEmail);
const validateMinLength8 = validate(minLength(8));
console.log(validateRequired("")); // Output: false
console.log(validateEmail("test@example.com")); // Output: true
console.log(validateMinLength8("password123")); // Output: true
Here,
- The validate function:
- This is a curried function that takes a validation
rule
first. - Then it takes a
value
to check against thatrule
. - It returns the result of running the
rule
on thevalue
(true
orfalse
).
- This is a curried function that takes a validation
- Validation rules:
isRequired
: checks if a value is not empty or null.isEmail
: checks if the value looks like a valid email using a regex.minLength
: a curried function itself that takes a minimum length and returns a function to check if the value’s length meets that minimum.
- Creating specialized validators:
validateRequired
is a function that checks if a value is present.validateEmail
checks if a value is a valid email.validateMinLength8
checks if a value has at least 8 characters.
- Using the validators:
console.log(validateRequired("")); // false (empty string is not valid) console.log(validateEmail("test@example.com")); // true (valid email) console.log(validateMinLength8("password123")); // true (length is >= 8)
Example 5: Data Processing Workflow
const map = (fn) => (array) => array.map(fn);
const filter = (fn) => (array) => array.filter(fn);
const reduce = (fn) => (initial) => (array) => array.reduce(fn, initial);
const numbers = [1, 2, 3, 4, 5];
const double = map((x) => x * 2);
const filterEven = filter((x) => x % 2 === 0);
const sum = reduce((acc, x) => acc + x)(0);
// Create a processing workflow
const processNumbers = (numbers) => sum(filterEven(double(numbers)));
console.log(processNumbers([1, 2, 3, 4, 5]));
Here,
- Curried helper functions:
map(fn)(array)
appliesfn
to each element.filter(fn)(array)
keeps elements that passfn
.reduce(fn)(initial)(array)
combines elements usingfn
starting withinitial
.- Each takes one argument at a time (currying).
- Specialized functions:
double
: doubles each number.filterEven
: keeps only even numbers.sum
: adds all numbers starting from 0.
- Processing workflow,
const processNumbers = (numbers) => sum(filterEven(double(numbers)));
:- First,
double(numbers)
doubles every number. - Then,
filterEven(...)
keeps only the even numbers. - Finally,
sum(...)
adds the filtered numbers.
- First,
- Example:
- Input:
[1, 2, 3, 4, 5]
- Double:
[2, 4, 6, 8, 10]
- Filter even:
[2, 4, 6, 8, 10]
(all are even after doubling) - Sum:
2 + 4 + 6 + 8 + 10 = 30
- Input:
Common Mistakes for Currying
-
Overusing Currying: Not every function needs to be curried. Use it when it provides clear benefits.
-
Readability Issues:
// This might be hard to read const result = curriedFunction(a)(b)(c)(d)(e); // Consider this instead const partiallyApplied = curriedFunction(a)(b); const result = partiallyApplied(c)(d)(e);
Performance Considerations for Currying
Curried functions create extra functions at each step.
This is usually fine, but in performance-critical code (like tight loops or real-time apps), it can slow things down slightly. If speed really matters, prefer regular functions or avoid currying inside loops.
👉 Next tutorial: Memoization