Solving the essential JavaScript interview question
Exploring some potential solutions to the essential JavaScript Interview question and some further reading.
This is Part 2 of Presenting the essential JavaScript interview question
Just to remind ourselves of the problem at hand, here’s the code again for reference.
(function() {
const numbers = [0.1, 0.2, 0.3];
let sum = 0;
for (var i = 0; i < numbers.length; ++i) {
setTimeout(() => {
sum += numbers[i];
}, 0);
}
console.log(sum === 0.6);
})();
There are actually two problems that need to be solved in this exercise.
Problem 1
We need to sum the values in the numbers
array and assign that value to the sum
variable.
There are a number of ways we can accomplish this. I’ll just cover a couple and point you in the right direction for others, otherwise we could be here for days!
1. Remove the setTimeout
By removing the setTimeout(() => { .. }, 0);
, the enclosed operation takes place immediately inside the current execution context, rather than being added to the message queue.
This means that sum += numbers[i];
has access to the i
variable each time that it is incremented and retrieves each item from the numbers
array.
Thus sum
becomes the sum of each item in the numbers
array.
(function() {
const numbers = [0.1, 0.2, 0.3];
let sum = 0;
for (var i = 0; i < numbers.length; ++i) {
sum += numbers[i];
}
console.log(sum === 0.6);
})();
2. Use Array.prototype.reduce
This is a classic reducer pattern, so what better way to solve it than to use reduce
?
This functionality exists in all popular functional programming libraries for JavaScript, such as Lodash or Underscore.
It’s also available directly on the Array
prototype in JavaScript!
(function() {
const numbers = [0.1, 0.2, 0.3];
let sum = numbers.reduce((a, c) => a + c);
console.log(sum === 0.6);
})();
Read: Array.prototype.reduce() - MDN web docs
Other potential solutions
- Create an implicit closure by initialising
i
withlet
. - Create an explicit closure by wrapping the
setTimeout
in an IIFE.
Problem 2
You may have noticed that even after implementing either of the previous solutions, we still have the problem that our assertion console.log(sum === 0.6)
returns false
.
This is due to the standard that JavaScript uses for its Number
type - IEEE Standard 754.
This does not actually store the number we have, but a floating point representation of that number. This makes any assertion we make unreliable.
More often than not, the number we actually end up with after summing 0.1
, 0.2
, and 0.3
, is actually 0.6000000000000001
. This is pretty close to 0.6
but equality doesn’t deal with pretty close, it deals with being equal.
So, how can we fix this? Again, I’ll cover a couple of solutions then point you in the right direction for others.
I’ll use Solution 2 from above as the basis for these solutions, since I believe this is the best solution for the first problem.
1. Use Number.prototype.toFixed
Since we know that we’re evaluating the value pointed to by sum
against 0.6
- a number to one decimal place - we can use toFixed
to ensure that the value sum
points to is to one decimal place too.
(function() {
const numbers = [0.1, 0.2, 0.3];
let sum = numbers.reduce((a, c) => a + c).toFixed(1);
console.log(sum === 0.6);
})();
Returns
false
…huh, why didn’t that work? 🤔
Well, illogical as it may sound, Number.prototype.toFixed
doesn’t actually return a value of the Number
type, it returns a string representation of that number.
So, we then have to parse that string back into the Number
type to get a number. For this purpose, we’ll use Number.parseFloat
.
As a side note,
parseFloat
is actually available on the global (orwindow
) object, but was added as a static method to theNumber
object in ES6 (ES2015).
(function() {
const numbers = [0.1, 0.2, 0.3];
let sum = Number.parseFloat(numbers.reduce((a, c) => a + c).toFixed(1));
console.log(sum === 0.6);
})();
Returns
true
…happy days! 😁
2. Use Math.round
or Math.floor
or Math.ceil
or Math.
…?
While integers in JavaScript are also represented using floating point, as specified by the IEEE Standard 754, they are a lot more reliable (at least small ones are).
So another approach may be to multiply each number by, say 10, then floor it, then divide by 10 afterwards, e.g.
(function() {
const numbers = [0.1, 0.2, 0.3];
let sum = numbers
.map(number => Math.floor(number * 10))
.reduce((a, c) => a + c) / 10;
console.log(sum === 0.6);
})();
Conclusion
I hope that after these last two posts about this simple interview exercise, I’ve managed to convince the naysayers or at the very least, enlightened somebody to a concept they had not previously been aware of.
Hefty hopes and high goals, I know. But you know what they say… well, you probably do, I don’t, so…
[ insert appropriate quote here ]
As previously mentioned, there are numerous ways to solve this problem. If solved it differently then I’d love to hear about it.
- Presenting the essential JavaScript interview question
- Wait for content to be updated and rendered in AngularJS 1.x
- Can you give a realistic task estimate?
- Creating a maintainable gulpfile.js
- JavaScript is a compiled language?
- Integrating ESLint settings with WebStorm
- What are object prototypes? [Part 1]
- Why participate in #100DaysOfCode?
- Finding a decent static site generator
- Now