Math.max() without Arguments
I posted the following poll on Twitter asking what would be returned if you called JavaScript's Math.max()
function without any arguments.
🏆 #JavaScript Quiz time! (no cheating) 🏆
— west, donavon west™️ (@donavon) March 20, 2020
We all know that `Math.max(1, 2, 3)` will return 3.
But what is the result if you call `Math.max()` without any arguments. Bonus points if you can explain why in the replies.
I gave 4 possible answers: undefined
, -1
, 0
, and -Infinity
.
I'm just going to go ahead and spoil it for you. The answer is -Infinity
(about half of you got it right, yay! 👏). But why?
Let's dive into the possible JavaScript-based implementations of Math.max
.
Using reduce to return -Infinity
JavaScript already has one, but if I were to write a max
function from scratch, this is how I would do it.
const max = (...values) =>
values.reduce((max, value) => (value > max ? value : max), -Infinity);
The function accept a single rest parameter called values
, which will be an array. reduce
is aptly named as it works to translate an array into a single value. We seed the initial maxValue
value with -Infinity
.
With each iteration through the array, we return value
(if it is greater than maxValue
) or maxValue
. When all values have been compared, we return maxValue
to the caller.
So because we seeded maxValue
with -Infinity
if there are zero items in the array, we return the initial value of maxValue
(i.e. -Infinity
).
Let's test our code by calling it with some sample data.
assert(max() === -Infinity, 'Should be -Infinity');
assert(max(-1000) === -1000, 'Should be -1000');
assert(max(1, 2, 3) === 3, 'Should be 3');
Yep. They all pass!
This is a short and sweet implementation.
Using reduce to return 0 (or -1)
If we took our code above and replaced the initial value of -Infinity
with 0
, the code would still work. Or would it?
Let's see this by running our tests again – changing the first one to check for zero instead.
assert(max() === 0, 'Should be 0');
assert(max(-1000) === -1000, 'Should be -1000');
assert(max(1, 2, 3) === 3, 'Should be 3');
As you see, calling max()
without any arguments did correctly return 0
, but we get an error with the test for -1000
.
AssertionError: Should be -1000
Why is max(-1000)
failing? What do you think this is returning? It is errantly returning 0
instead of -1000
. This is because a value
of -1000
is not greater than a maxValue
of 0
.
People tend to think in terms of positive numbers. Returning 0
would break some valid conditions as we've shown.
Using reduce to return undefined
What if we wanted to return undefined
? This isn't a bad idea (my 8-year-old guessed this when I "quizzed" my family) but would add complexity to our solution above.
Let's take a look.
const max = (...values) =>
values.reduce((max, value) => {
if (max === undefined) {
return value;
}
return value > max ? value : max;
}, undefined);
You see, we would explicitly need to check if maxValue
was undefined
inside of our loop. It works, but conditions add time and code complexity.
In conclusion
We've learned that returning -Infinity
or undefined
are the only possible valid conditions of the four I presented in my Twitter poll.
However, -Infinity
greatly reduces code complexity and IMO, is the best solution.
Follow up from Brendan Eich
It turns out that my guess was right! This is exactly how Math.max
is implemented. I received the following tweet from the father of JavaScript, Brendan Eich.
That's right. Math.max in ur-JS and ES1&2 took only two parameters, but ES3 generalized to compute maximum of a variable number of arguments. This motivates -∞ as the max of the empty set (0 args) basis case, as you showed.
— BrendanEich (@BrendanEich) March 21, 2020