Implement currying using javascript

Let's talk about another common frontend interview question which is implementing currying. This is a simple and straightforward question most of the big companies ask and even I myself have asked this question many times.

Before we go ahead with the implementation, let's first understand what currying is.

What's currying in javascript?

It's a method with which you can transform a given function to another depending on the use cases. In short, it doesn't call any function, it just transforms it.

Let's start with a simple example. We have this below curry wrapper function:

function curry(a) {
    return function (b) {
        return function (c) {
            return a + b + c;
        }
    }
}

The curry method takes a parameter a and returns a function that takes a parameter b and that returns a function that takes the parameter c and returns the sum of a, b and c

You can consume the curry function using one of the ways:

  1. const sum = curry(1)(2)(3) results in 6.

  2. const sumOne = curry(1) returns a function that can then be used anytime like: const sumTwo = sumOne(2) which again returns a function that can be used like: const finalSum = sumTwo(3) and this returns the sum of all.

Notice that you never had to pass the previous parameters and yet they are remembered by the flow. This is possible with the concept of closures in javascript. (Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)

Ok, now you understood what currying is and how you can do a basic currying implementation. Let's get to a bit more complex question that is asked in the interviews. Please understand that there can be different variations of it but the underlying concept remains the same.

Interview Question

function sum(a, b, c, d, e) {
    return a + b + c + d + e;
}

function curry(fn) {
    // Fill this function
}

The interviewer would generally provide you with the above snippet and would ask you to implement the curry function so that when you do the following:

const sumOfAll = curry(sum);
sumOfAll(1)(2)(3)(4)(5) // Returns 15

it returns the output as 15.

Ok, now that we got the expectations, we want to clarify 2 main things here with the interviewer.

  1. Is it always sum function? - No, it can be any function.

  2. Will it always take 5 parameters? - No, it can have a varying number of parameters or none at all.

With the above 2 clarifications, we can be sure that we cannot go with the hard-coded route like how we learned initially.

For [1], we are anyway getting the fn as an argument, we can simply invoke based on the condition.

For [2], we would have to know when to invoke the fn. In the case of sum, it has 5 parameters and we need to invoke it after we have received 5 parameters. Until that we have to keep returning a function.

In order to know how many arguments a function is expecting, we can use a built-in helper called Function.length (Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length)

For example:

  1. const sum = (a, b) => { ... }

    sum.length would return 2 in this case.

  2. const someFunc = () => { ... }
    someFunc.length would return 0.

Now, let's apply the understanding to our problem as and when required.

Getting back to the original problem, let's start by getting some basic setup ready.

Implementation

function curry(fn) {
    let total = fn.length;
    function inner(a) {
        //...
    }
    return inner;
}

What we have essentially done is return a function once we call the main curry wrapper. We also stored the total number of arguments the function can support in the total variable.

Now, we need to fill the inner function as the subsequent calls will go inside that function.

function inner(a) {
    --total;
    if (total === 0) {
      return fn.call(null, a);
    }
    fn = fn.bind(null, a);
    return inner;
}

You can write this inner implementation in multiple ways, I personally prefer this way as it is easier to write down.

So, what happens here is:

  1. We are decrementing the total variable's value every time the inner function is called assuming that the parameter is being passed every time.

  2. We are checking if the total value reached 0. If yes:

    1. We have to call the specified function with the value.

    2. If not, we have to keep binding the value to the function.

  3. We return the same inner function so that the process can be repeated for n number of times.

Putting all together, we get:

function curry(fn) {
  let total = fn.length;
  function inner(a) {
    --total;
    if (total === 0) {
      return fn.call(null, a);
    }
    fn = fn.bind(null, a);
    return inner;
  }

  return inner;
}

Calling the above method with something like:

const sumOfAll = curry(sum); // sum is the function that we already have.
console.log(sumOfAll(1)(2)(3)(4)(5)); // Returns 15.

Now, the interviewer is adding a new use case to support the following as well:

const sumOfAll = curry(sum);
console.log(sumOfAll(1, 2, 3)(4)(5)); // Returns 15.

Now the argument need not be just single always, it can be multiple as well. Can you modify our existing code to support the new use case? If you can, please share your code in the comments so that I can have a look. Also, if you want me to share the code for that, please let me know as well. I can edit and add the code for the same.

You can find the full code here - https://gist.github.com/thearunkumar/7dbfdafaa1bdeffdde6cd60d3e57ccba

Cheers,

Arunkumar Sri Sailapathi.