TQ
dev.com

Blog about software development

Subscribe

Boolean comparison in JavaScript has a bug

16 Oct 2016 - by 'Maurits van der Schee'

A few days ago, a colleague pointed out some unexpected behavior in JavaScript. After a few hours of investigation this weekend I think I have found the cause: a bug in boolean comparison. This post shows my investigation and will help you to reach that same conclusion.

A tricky question

What does the following expressions evaluate to?

> [] && [] == false
true

Yes, it is "true"! If you don't believe me, try it. The question that is addressed in the remainder of this post is: "Really? Why?!"

Empty array evaluates to true

You may think that empty arrays evaluate to "false" and "false && false" evaluates to "false" leaving "false == false" which then evaluates to "true". That would have been an easy explanation, but it is wrong. In JavaScript empty arrays evaluate to "true" (when converted to a boolean) as you can see here:

> ([]?true:false)
true
> Boolean([])
true

Let's test a few script languages to see what they evaluate empty array to, when used as a boolean expression:

$ nodejs -e 'console.log([] ? "true" : "false")'
true
$ php -r 'print ([] ? "true" : "false");'
false
$ perl -e 'print ([] ? "true" : "false")'
true
$ perl -e 'print (() ? "true" : "false")'
false
$ ruby -e 'print ([] ? "true" : "false")' 
true
$ ruby -e 'print (() ? "true" : "false")' 
false
$ python -c 'print ("true" if [] else "false")'
false

As you can see the empty array in Perl and Ruby also evaluates to "true", while empty lists evaluate to "false". So all languages choose the (in my opinion) more logical "false" value to evaluate to (in one way or another). When we replace the empty array with "true" the expression behaves as expected:

> true && true == false
false

It almost seems as if in this case the empty array is not evaluated to "true". That can't be the case or can it?

Operator precedence

I tried the following to find out whether or not this had to do with operator precedence:

> ([] && []) == false
true
> [] && ([] == false)
true

But since both evaluate to true, I think we can rule operator precedence out. We have to dig a little deeper.

Logical operator evaluation

I got the feeling it had something to do with the way script languages evaluate logical expressions.

$ nodejs -e 'console.log((2 && 3) > 2)'
true
$ php -r 'var_export((2 && 3) > 2);'
false
$ perl -e 'print ((2 && 3) > 2)'
1
$ ruby -e 'print ((2 && 3) > 2)'
true
$ python -c 'print ((2 and 3) > 2)'
True

Instead of returning true or false most scripting languages (PHP excluded) return the first "falsy" (evaluates to false) or the second "truthy" (evaluates to true) argument of the "&&" or "and" expression. You can see that here in more detail:

> 0 && 1
0
> 2 && 3
3

Now let's see whether or not logical operator evaluation matters by replacing the "[]" (empty array) by a "1" (one), another non-boolean that also evaluates to "true":

> Boolean(1)
true
> 1 && 1 == false
false

So somehow if we replace "[]" (empty array) with "1" (one) we get correct results. Since "1" (one) does not show the (strange) behavior that the empty array shows, the behavior seems to be unrelated to the way logical operators are evaluated.

Comparison operator evaluation

So, let's approach it from the other side now. The only way we can construct the result "true" is when the "==" (comparison) operator fails to convert correctly to the type of the left or right hand side of the comparison expression. Let's try all possible paths:

> Boolean([])==Boolean(false)
false
> Number([])==Number(false)
true
> String([])==String(false)
false
> Object([])==Object(false)
false

You would expect the first to happen (or the last), because that seems the logical way to convert both sides to the same type. Apparently this is not true and the comparison expression between an object and a boolean is evaluated as a numerical comparison (a type that is unrelated to both the left and the right side). Don't you believe me? Read on.

EcmaScript 6 standard

Let's see what the EcmaScript 6 standard states on "Equality Comparison":

The comparison x == y [...] is performed as follows: [...] If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).

It states (as the first matching step of the comparison algorithm) that comparison with a boolean on the right hand side should be done by converting that boolean to a number, just like we expected from our analysis.

Conclusion

An empty array in JavaScript is evaluated to true when converted to type Boolean. But when comparing a boolean with an object neither the type of the left hand nor the type of the right side is chosen to evaluate the comparison in (instead Number is chosen). This may be wrong, but we have seen that it is in line with the specification in the EcmaScript standard.

> Boolean([])
true
> [] == false
true

Now that I studied the standard I was able to come up with a similar case. Can you now reason why this one evaluates to "true"?

> "0" == !"0"
true

The above JavaScript statements clearly show the problem and this 5 year old StackOverflow post confirms that the comparison was indeed done numerical. I don't expect a fix for this problem any time soon as this is a problem in the standard and not in the implementation.