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:
const sum = curry(1)(2)(3)
results in 6.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.
Is it always
sum
function? - No, it can be any function.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:
const sum = (a, b) => { ... }
sum.length
would return 2 in this case.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:
We are decrementing the
total
variable's value every time the inner function is called assuming that the parameter is being passed every time.We are checking if the
total
value reached 0. If yes:We have to call the specified function with the value.
If not, we have to keep binding the value to the function.
We return the same
inner
function so that the process can be repeated forn
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.