Understanding Currying in JavaScript: Functional Programming Simplified

Piyush kantPiyush kant
5 min read

Introduction

Functional programming has revolutionized the way we write cleaner, more efficient code in JavaScript, and one of its key concepts is currying. Although currying might seem complex at first, it’s a powerful tool that simplifies functions and enhances reusability.

In this article, we will explore:

  • What currying is and how it works in JavaScript.

  • Techniques to implement currying.

  • Practical examples and use cases for currying in everyday code.

By the end of this guide, you’ll be well-equipped to use currying to write cleaner, more modular JavaScript.


1. What is Currying & How Does It Work?

Currying is a functional programming technique where a function that takes multiple arguments is transformed into a series of functions, each taking a single argument. In essence, it breaks down a function with multiple parameters into multiple nested functions, each handling one argument at a time.

Basic Example of Currying

Let’s start with a non-curried function:

function add(a, b) {
  return a + b;
}

console.log(add(2, 3)); // 5

This is a standard function that accepts two arguments, a and b. Now, let’s convert it into a curried function:

function add(a) {
  return function(b) {
    return a + b;
  };
}

const addTwo = add(2); 
console.log(addTwo(3)); // 5

In the curried version, the add function takes one argument, a, and returns another function that takes b and adds the two numbers. This function can now be invoked in two stages: first with a, and later with b. This makes the function more modular and flexible for reuse.

Why Currying is Useful

  • Partial Application: With currying, you can fix some arguments of a function and get a new function with fewer arguments. This is helpful when you want to reuse a function but with some preset values.

  • Higher-order Function Compatibility: Currying allows functions to be passed to other functions more easily, especially in contexts like map or filter.


2. Implementing Currying Techniques in JavaScript

While the manual currying technique (shown above) works, JavaScript allows us to automate this process. Let’s explore a few ways to implement currying effectively.

Manual Currying

The manual approach is a simple way to understand the mechanics of currying. You can create a series of nested functions to break down the arguments:

function multiply(a) {
  return function(b) {
    return function(c) {
      return a * b * c;
    };
  };
}

console.log(multiply(2)(3)(4)); // 24

Here, each function call returns another function until all arguments are provided, resulting in the final calculation.

Currying with Arrow Functions

Using arrow functions can make the syntax more concise:

const multiply = (a) => (b) => (c) => a * b * c;

console.log(multiply(2)(3)(4)); // 24

This version is more compact and cleaner, particularly for simple operations.

Automatic Currying with Utility Libraries

There are many utility libraries (like Lodash) that can automatically curry any function. Here’s an example using Lodash’s curry function:

const _ = require('lodash');

const add = (a, b, c) => a + b + c;
const curriedAdd = _.curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6

With _.curry, the function can be invoked in multiple ways, enhancing flexibility.

Currying with Rest Parameters

Using rest parameters, you can create a more dynamic curried function that works with any number of arguments:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);

console.log(curriedSum(1)(2)(3)); // 6

This implementation of currying allows for flexible argument passing, even with varying lengths of arguments.


3. Practical Examples & Use Cases for Currying

Currying shines in situations where you need to reuse functions with some preset parameters. Let’s look at a few real-world use cases.

Example 1: URL Builder

A common use case is creating URL endpoints. Currying can help you build URLs by pre-filling parts of the URL.

const buildUrl = (protocol) => (domain) => (path) => `${protocol}://${domain}/${path}`;

const httpsGoogle = buildUrl("https")("google.com");
console.log(httpsGoogle("search")); // https://google.com/search

Here, the buildUrl function can be partially applied to create a function specifically for building Google URLs, improving reusability.

Example 2: Event Handlers

Currying is particularly useful in event handling, where you may want to preset certain event data before passing the handler to multiple elements.

const handleClick = (size) => (event) => {
  console.log(`Button size: ${size}`);
  console.log(`Event type: ${event.type}`);
};

document.querySelector("#small-btn").addEventListener("click", handleClick("small"));
document.querySelector("#large-btn").addEventListener("click", handleClick("large"));

In this example, the handleClick function is curried, allowing us to pass different sizes to different buttons while still keeping the event handler reusable.

Example 3: Mathematical Operations

Currying also simplifies mathematical operations, where you may need to perform similar operations repeatedly with different sets of numbers.

const discount = (discountRate) => (price) => price - price * discountRate;

const tenPercentOff = discount(0.1);
console.log(tenPercentOff(100)); // 90
console.log(tenPercentOff(200)); // 180

The discount function is curried to create reusable discount functions that apply the same discount rate to various prices.

Example 4: Function Composition

Currying can also facilitate function composition, where multiple functions are combined to transform data step-by-step.

const add = (x) => x + 1;
const multiply = (x) => x * 2;

const compose = (f, g) => (x) => f(g(x));

const addThenMultiply = compose(multiply, add);

console.log(addThenMultiply(3)); // 8

In this case, currying helps compose two smaller functions into a single function that first adds, then multiplies.


Conclusion

Currying simplifies complex operations by breaking them down into smaller, more manageable parts. This technique enhances the reusability and modularity of functions in JavaScript, making code cleaner and more adaptable.

By implementing currying techniques, you can take advantage of partial application, reduce code duplication, and write more expressive, functional JavaScript. Whether you’re working with event handlers, URL builders, or mathematical operations, currying unlocks a new level of flexibility in your code.

As you continue mastering functional programming in JavaScript, currying will become an essential tool in your toolkit, helping you craft elegant, reusable code with ease.


0
Subscribe to my newsletter

Read articles from Piyush kant directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Piyush kant
Piyush kant