Currying

Learn what currying is in JavaScript, why it’s useful, and how to use it with real-world examples like validation, events, and data transformation.

Loading...
Currying

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 remembers a = 2.
  • Then, you call that returned function with 3.
  • It adds 2 + 3 and gives you 5.
  • So instead of calling add(2, 3), you call curriedAdd(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, and c.
    • Calculates and returns the result directly.
    • It's simple to use, but you can't easily reuse part of it with different values.
  • Curried function function curriedAdd(a) {...}:
    • curriedAdd(1) returns a function that remembers a = 1.
    • That function is called with 2 remembers b = 2.
    • That function is called with 3 now has a + 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 runs fn, 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 product x * y.
  • Creating specialized functions, const double = multiply(2); and const triple = multiply(3);:
    • When you call multiply(2), it returns a function that multiplies any number by 2.
    • This returned function is saved as double.
    • Similarly, multiply(3) returns a function that multiplies any number by 3, saved as triple.
    • These are specialized versions of the multiply function, with one argument already fixed.
  • Using the specialized functions, console.log(double(5)); and console.log(triple(4));:
    • Calling double(5) is the same as calling multiply(2)(5).
    • It multiplies 2 * 5 and returns 10.
    • Similarly, triple(4) returns 3 * 4 = 12.
  • 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"); and const sayGoodbye = greet("Goodbye");:
    • sayHello is now a function that remembers greeting = "Hello".
    • sayGoodbye remembers greeting = "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 returns true or false 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 given predicate.
  • 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.

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.
  • 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 the handler.
  • 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 that rule.
    • It returns the result of running the rule on the value (true or false).
  • 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) applies fn to each element.
    • filter(fn)(array) keeps elements that pass fn.
    • reduce(fn)(initial)(array) combines elements using fn starting with initial.
    • 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.
  • 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

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

Support my work!