Currying in JavaScript

The concept of currying in JavaScript is a functional technique in which functions can be partially applied. What this essentially means is that a function doesn’t necessarily need to have all of its arguments passed in. If all of them are not returned then a function is returned from that function waiting for the rest of the arguments.

This in turn leads to tighter, more compact code.

Here is a simple example of a function that expects two arguments, a, and b, and returns the sum of the two.

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

This function can be curried to return a function.

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

With the curried function above, functions can be built that are partially applied as such:

const add10 = function(10) {
  return function(b) {
    return 10 + b;
  }
}

add10(5) // 15;
add10(0) // 10;

Application in the real world

Recently at my current employer, Jet, I was working on building the Buy Box (as seen below) for the product details page (the page that lists a products description, and pricing breakdown). After working out a potential solution for building this component I was able to refactor my solution and use currying to build make the code more efficient.

Jet Buy Box

Depending on the product, Jet allows a user to select from various discounts, including opting out of free returns, using a debit card, and a combination of the two.

Each one of these different discounts uses a base price and then subtracts the discounts accordingly to give the final price.

In the example above:
startingPrice: $9.19
waive returns: savings of .09 for a final price of $9.10
debit card: savings of .14 for a final price of $9.05
combination of the two: savings of .23 for a final price of $8.96

One-way to write the functions to generate each one of the savings are…

function calcFreeReturnsPrice(startingPrice, waiveReturns) {
    return (startingPrice - waiveReturns).toFixed(2);
}

function calcDebitPrice(startingPrice, debitSavings) {
    return (startingPrice - debitSavings).toFixed(2);
}

function calcFreeReturnsAndDebit(startingPrice, waiveReturns, debitSavings) {
    return (startingPrice - waiveReturns - debitSavings).toFixed(2);
}

With this solution, there is a lot of code repeat, and the code isn’t very DRY. All three functions expect the startingPrice argument to be passed in, and each is returning the startingPrice subtracted from the savings passed in. In addition, if more savings were added in the future this code would continue to get bloated.

To make this code tighter, and allow for additional savings to be added in the future, we can leverage currying, as well as some ES6 operators to our advantage.

// Curried function that accepts a startingPrice argument
const calcSavings = function(startingPrice) {
    /**
     * calcSavings returns a function looking for X number of arguments
     * By using the ES6 spread operator this function can accept any number of arguments
     * which will get become an array of everything thats passed in
     */
    return function(...savings) {
        /**
         * Use the reduce function to iterate over each value
         * subtracting the previousValue from the currentValue
         * The startingPrice which is accessed through a closure of the curried function
         * will be the starting value each time this is called
         */
        savings.reduce((previousValue, currentValue) => previousValue - currentValue, startingPrice);
    }
}
// Comments removed
const calcSavings = function(startingPrice) {
    return function(...savings) {
        savings.reduce((previousValue, currentValue) => previousValue - currentValue, startingPrice);
    }
}

What once took 3 functions to accomplish, has now been achieved with this one calcSavings function. With currying the functions can now be called as so:

const basePrice = calcSavings(9.19);

const waiveReturns = basePrice(.09);
const useDebitCard = basePrice(.14);
const waiveReturnsAndDebit = basePrice(.09, .14);

Here is what the break down of the waiveReturnsAndDebit call will look like:

const calcSavings = function(startingPrice) {
    return function(...savings) {
        savings.reduce((previousValue, currentValue) => previousValue - currentValue, startingPrice);
    }
}

/**
 * startingPrice = 9.19
 * This was passed in through the basePrice variable that was declared before setting waiveReturns
 * this creates a closure over the startingPrice variable
 */
const calcSavings = function(9.19) {
    return function([.09, .14]) {
        /**
         * First reduce call:
         *    The startingPrice begins as the previousValue in the first call
         *    The currentValue is the first index of the savings array
         * [.09, .14].reduce((9.19, .09) => 9.19 - .09, 9.19)
         *
         * Second reduce call
         * [.14].reduce((9.1, .14) => 9.1 - .14, 9.19)
         *
         * Once all the savings have gone through the reduce function
         * The final value of 8.96 is returned
         */
        savings.reduce((previousValue, currentValue) => previousValue - currentValue, startingPrice);
    }
}

Should Jet in the future look to add more discounts, because of currying (and with the help of reduce) this function allows for any number of discounts to be added, without having the change the logic for how they are deducted from the starting price.

By utilizing currying, functions become smaller, code becomes tighter, and repitiion decreases.

Helpful Links

Sitepoint: A Beginner’s Guide to Currying in Functional JavaScript
MDN: Array.prototype.reduce()