Eugene Karataev

Posted on

Interview Question Journey - Currying, Closures, Type Coercion, oh my 😱

This post is based on the true story with minor changes for readability.

Let's say you're on interview for frontend developer position. Interviewer asks you to write a function to add two numbers.
That's easy and you come up with

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

Next you're asked to modify the function to the `add(1)(2)` syntax.
Well,

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

More parentheses! `add(1)(2)(3)` should return 6.
No problem:

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

So far so good. Next task is to write `add` function with the requirements:

``````+add(1)(2) // should return 3
``````

It's clear that the previous solution should be rewritten to accept any amount of parentheses. We also notice the plus sign before the `add` function which leads us to think about type coercion.
What if we always return a function from `add` and coerce it to a primitive number when necessary? JavaScript calls valueOf method to convert a function to a primitive number.

``````function add(a) {
}

return 42;
}

``````

We return function `add` from the function `add` and overwrite it's `valueOf` method to return a constant number when coerced to a primitive.
We don't get the correct result yet, but the big step is made. We don't get a runtime error and are able to return a number! The next step is to correctly sum the numbers.
Somehow we should accumulate the arguments the `add` function was called with. Let's start the easiest way with counter.

``````let counter = 0;
counter += a;
}
return counter;
};

console.log('Should be 3', +add(1)(2)); // 3
console.log('Should be 6', +add(1)(2)(3)); // 9
``````

The first result is correct, but the second is wrong, because the counter was not resetted after the first coercion. Let's fix this.

``````let counter = 0;
counter += a;
}
let temp = counter;
counter = 0;
return temp;
};

console.clear();
console.log('Should be 3', +add(1)(2)); // 3
console.log('Should be 6', +add(1)(2)(3)); // 6
``````

Great! Now everything works as expected! But the code isn't great, we can do better. Let's refactor 🛠

``````function add(a) {
let counter = a;
function inner(b) {
counter += b;
return inner;
}
inner.valueOf = () => counter;
return inner;
}

console.log('Should be 3', +add(1)(2)); // 3
console.log('Should be 6', +add(1)(2)(3)); // 6
``````

Awesome! ✨ The result is correct and the code is nice. We created function `inner` inside the `add` and return it. The `counter` variable is in closure and there is no need to reset it like in the previous example.
Now it's possible to write expressions like this:

``````let result = add(1)(2) + add(1)(2)(3) + add(1)(2)(3)(4) + add(1)(2)(3)(4)(5);
console.log(result); // 34
``````

And get the correct result.

Sung M. Kim • Edited

Thanks for the fun post Eugene.

I was playing around to implement it with Function#bind but it seems like it's not possible to know when user is "done" with arguments and calculate the "add" without knowing the "arity" (parameter count).

I ended up using "done" as an end condition but one can probably specify the arity initially as a second argument of `curriedAdd(arity, value)` and compare the arity against the argument length.

But then at this point, it will be a partial application, not a currying...

And then I was surprised 😮 to learn from your post that the type-coercion using `+` causes `.valueOf` to be called.

.

So I've tried for the 3rd time to make the function work as yours do using `.valueOf`.

This post got me really thinking about currying and steps to reach your conclusion as well as mine.

Below is the source above.

``````function curriedAdd(v1) {
if (arguments[arguments.length - 1] === "done") {
console.log(`done...`)
return Array.prototype.slice
.apply(arguments, [0, arguments.length - 1])
.reduce((acc, v) => acc+=v)
};
console.log(`v1`, v1, `arguments`, ...arguments, 'arg.length', arguments.length)
}

// "-1" to account for the arity
if (arguments.length - 1 === arity) {
return Array.prototype.slice
.call(arguments, 1)
.reduce((acc, v) => acc += v, 0)
};
}

inner.valueOf = () => [...arguments].reduce((acc, v) => acc += v, 0);
return inner;
}

``````

Eugene Karataev

Wow, thanks for the such detailed comment! I did not think about `bind`ing and collecting arguments with each function call. JS is so flexible language that allows many ways to solve a problem. Thanks for sharing your path!

Sung M. Kim

I found your approach your readable as it is more intentional what the code is doing 🙂

Josh Brown

Honestly I think this kind of question isn't suitable for an interview and is an "edge case" of programming knowledge. You'd never write code like this in the real world, therefore it has no commercial value and seems daft to test in an interview.

In my opinion interview questions/tasks should be "real world" and allow the candidate to show their critical thinking and problem solving abilities, rather than testing their understanding of language nuances.

Eugene Karataev

Well, I was not happy with this task as well. It took me about 40 minutes to solve it 😂
Other tasks were closer to the problems a programmer solves in his/her day-to-day work.
But it was very interesting for me to solve this task and it was the inspiration for this post.

Josh Brown

Yeah - you did a great job to be able to solve it!

Nicolas Marcora

Nice one! I agree this is a very weird interview question to be asking, but since we're already doing crazy stuff, you inspired me to go further. This version can accept multiple numbers or callbacks at once, and will add/apply all to the count.

``````const add = (...args) => {
let count = 0

const applyArguments = args => args.reduce((acc, arg) => {
if (typeof arg === 'function') return arg(acc)
if (typeof arg === 'number') return acc + arg
return acc
}, count)

const adder = (...args) => args[0] === undefined
? count

}

const double = n => n * 2

const timesTen = n => n * 10

// 442

// 442

Thanks for sharing, I like how you go deeper with `add` functionality.