In the world of functional programming, techniques that enhance code reusability and modularity are highly valued. One such technique that has found its way into JavaScript is currying. But what exactly is currying, and is it really just a chain of functions? In this comprehensive guide, we’ll explore the concept of currying in JavaScript, how it works, its benefits, and how you can implement it in your code.

Many developers confuse currying with other functional programming patterns. Let’s clear up these misconceptions and dive deep into why currying is becoming an essential tool in the modern JavaScript developer’s toolkit.

What is Currying in JavaScript?

Yes, currying in JavaScript is indeed a chain of functions. Currying is an advanced technique commonly used in functional programming that transforms a function with multiple arguments into a sequence of functions, each taking only one argument at a time. The concept can be represented as:

1
log(a, b, c) => log(a)(b)(c)

Named after mathematician Haskell Curry, who formalized this concept in the 1930s, currying doesn’t actually call a function—it transforms it. While many developers assume currying automatically executes a function, it’s important to note that currying merely restructures how arguments are passed.

How Currying Works in JavaScript

To understand currying, let’s start with a basic example of a standard function that adds three numbers:

1
2
3
4
5
6
// Regular function
function addition(a, b, c) {
  return a + b + c;
}

console.log(addition(1, 2, 3)); // Output: 6

Now, let’s transform this into a curried version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Curried function
function addition(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

console.log(addition(1)(2)(3)); // Output: 6

In this curried version, each function takes a single argument and returns another function until all the required arguments are collected, at which point the final function performs the calculation and returns the result.

Here’s how the execution flows step by step:

  1. addition(1) returns a function that expects parameter b
  2. addition(1)(2) calls that returned function with argument 2, which returns another function expecting parameter c
  3. addition(1)(2)(3) calls the final function with argument 3, which calculates 1 + 2 + 3 and returns 6

ES6 Arrow Functions and Currying

With ES6 arrow functions, we can write the curried addition function in a more concise way:

1
2
3
4
// Curried function using arrow syntax
const addition = a => b => c => a + b + c;

console.log(addition(1)(2)(3)); // Output: 6

This syntax is not only shorter but also makes the currying pattern more readable for some developers, especially those familiar with functional programming concepts.

The Difference Between Currying and Function Chaining

While currying might look similar to function chaining at first glance, they are distinct concepts:

Function Chaining:

  • Each method in the chain operates on the same object
  • Returns the object itself (this) to enable further method calls
  • Common in jQuery and many fluent APIs
1
2
3
4
// Function chaining example
document.querySelector('div')
  .classList.add('active')
  .style.color = 'red';

Currying:

  • Transforms a function that takes multiple arguments into a sequence of functions
  • Each function returns a new function until the final value is returned
  • Focused on functional composition and partial application

Understanding this distinction is crucial for using both patterns correctly in your JavaScript applications.

Implementing Infinite Currying in JavaScript

One powerful application of currying is the ability to create functions that can accept an indefinite number of arguments. This is known as infinite currying:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function addition(val1) {
  return function(val2) {
    if (val2) {
      return addition(val1 + val2);
    }
    return val1;
  };
}

console.log(addition(1)(2)(3)(4)()); // Output: 10

Let’s break down how this works:

  1. addition(1) returns a function that takes val2
  2. If val2 is provided, the function returns a recursive call to addition with the sum of val1 and val2
  3. If val2 is falsy (such as when empty parentheses are used), the function returns the accumulated val1
  4. This pattern allows for an indefinite number of arguments to be added together
  5. The final empty parentheses () signal that we’re done adding values and want the final result

This approach gives you remarkable flexibility in how you call your functions.

Benefits of Currying in JavaScript

1. Partial Application

One of the most significant advantages of currying is partial application. This allows you to fix some arguments of a function and create a new function with fewer parameters:

1
2
3
4
// Create a partially applied function
const addTen = addition(10);
console.log(addTen(5)); // Returns a function
console.log(addTen(5)()); // Output: 15

This is incredibly useful when you have functions with common arguments that you don’t want to repeat.

2. Function Composition

Currying facilitates function composition, allowing you to combine multiple functions to create new functions:

1
2
3
4
5
6
const double = x => x * 2;
const addFive = x => x + 5;

// Compose functions
const doubleThenAddFive = x => addFive(double(x));
console.log(doubleThenAddFive(10)); // Output: 25

3. Enhanced Readability

For certain problems, currying can lead to more readable and maintainable code, especially when dealing with higher-order functions:

1
2
3
4
5
6
7
8
// Without currying
const filteredNumbers = [1, 2, 3, 4, 5].filter(function(num) {
  return num > 3;
});

// With currying
const greaterThan = pivot => num => num > pivot;
const filteredNumbers = [1, 2, 3, 4, 5].filter(greaterThan(3));

4. Debugging Simplicity

Curried functions are easier to debug because you can inspect each step of the process:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const debugAddition = a => {
  console.log(`First argument: ${a}`);
  return b => {
    console.log(`Second argument: ${b}`);
    return c => {
      console.log(`Third argument: ${c}`);
      return a + b + c;
    };
  };
};

debugAddition(1)(2)(3); // Logs each step and returns 6

5. Memoization Opportunities

Currying creates opportunities for memoization (caching results) at each step of the function chain:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function memoizedAddition() {
  const cache = {};
  
  return function(a) {
    if (cache[a]) return cache[a];
    
    console.log(`Calculating first step with ${a}`);
    cache[a] = function(b) {
      return a + b;
    };
    
    return cache[a];
  };
}

const add = memoizedAddition();
const addFive = add(5); // Calculation happens
const addFiveAgain = add(5); // Uses cached result

Real-world Use Cases for Currying

1. Event Handling

Currying is particularly useful for event handling with specific configurations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const handleEvent = eventType => element => callback => {
  element.addEventListener(eventType, callback);
  return {
    remove: () => element.removeEventListener(eventType, callback)
  };
};

const onClick = handleEvent('click');
const onClickButton = onClick(document.getElementById('myButton'));
const cleanup = onClickButton(e => console.log('Button clicked!'));

// Later, when needed
cleanup.remove();

2. API Request Configuration

When making API requests, currying can help create reusable request creators:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const createAPIRequest = baseURL => endpoint => options => {
  return fetch(`${baseURL}${endpoint}`, options)
    .then(response => response.json());
};

const myAPI = createAPIRequest('https://api.example.com');
const getUsers = myAPI('/users');

// Use it with different options
getUsers({ method: 'GET' }).then(data => console.log(data));

3. Data Validation

Currying can simplify complex validation chains:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const validate = schema => validationFn => data => {
  return validationFn(data, schema);
};

const validateUser = validate({
  required: ['name', 'email']
});

const validateEmailFormat = validateUser((data, schema) => {
  // Email validation logic
  return data.email.includes('@');
});

console.log(validateEmailFormat({ name: 'John', email: 'john@example.com' })); // true

Common Mistakes and Pitfalls

When working with currying in JavaScript, be aware of these common issues:

1. Performance Considerations

Excessive currying can lead to performance overhead due to the creation of multiple function closures:

1
2
// This can be inefficient for deeply nested currying
const deepCurry = a => b => c => d => e => f => g => a + b + c + d + e + f + g;

2. Readability Concerns

While currying can improve readability in some cases, it can also make code harder to understand for developers unfamiliar with functional programming:

1
2
// This might be confusing for some developers
const result = compose(map(double), filter(isEven), reduce(sum))(numbers);

3. Binding Issues

When using currying with methods that rely on this, be careful about context binding:

1
2
3
4
5
// Incorrect
const getName = person => person.getName();

// Correct
const getName = person => person.getName.call(person);

Converting Standard Functions to Curried Functions

To convert regular functions to curried ones, you can use a curry helper:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    
    return function(...moreArgs) {
      return curried.apply(this, args.concat(moreArgs));
    };
  };
}

// Usage
function addThreeNumbers(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(addThreeNumbers);

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

This curry helper supports both traditional currying (one argument at a time) and partial application (multiple arguments at once).

Conclusion

Currying in JavaScript is indeed a chain of functions, but it’s so much more than that. It’s a powerful functional programming technique that transforms multi-argument functions into a series of single-argument functions, enabling partial application, function composition, and more modular code.

As JavaScript continues to embrace functional programming paradigms, understanding currying becomes increasingly valuable for developing clean, reusable, and maintainable code. Whether you’re working on complex front-end applications or scalable back-end systems, currying provides a set of tools that can significantly enhance your programming arsenal.

While currying might seem complex at first, its principles are straightforward, and with practice, it becomes a natural part of functional programming in JavaScript. The next time you find yourself repeating similar function calls with shared arguments, consider whether currying might offer a more elegant solution.

So, is currying in JavaScript a chain of functions? Yes, but it’s a chain with purpose—a transformation that unlocks new patterns of composition, reuse, and abstraction in your code.