We have all been there… You compare two values using ==
, receive a totally unexpected result and wonder WTF is going on?! - Just Equality ==
operator at its finest.
Equality ==
operator has the following syntax.
x == y
It takes two operands, x
and y
, converts them into the same type, compares them by value and returns a Boolean
result.
Always use Strict Equality `===` operator. There is literally no benefit in using `==` instead of `===`, except if your goal is to introduce bugs to your codebase.
But you did not come here so I can tell you to start using ===
. Just look at the code below. You want to know how it can possibly be explained, don't you? - And yes, you should know it. After all, you are working with JavaScript and this is a JavaScript feature.
1'' == '0' // Result: false20 == '' // Result: true30 == '0' // Result: true45false == undefined // Result: false6false == null // Result: false7null == undefined // Result: true89[] == 0 // Result: true10[] == '0' // Result: false11['a', 'b'] == 'a,b' // Result: true1213NaN == NaN // Result: false14'\n\r\t' == 0 // Result: true
IsLooselyEqual algorithm
When you use ==
, under the hood, this algorithm kicks in and gives the result of comparison. So, yes, here lies the cure for our pain.
This algorithm is described in ECMAScript specification and basically every JavaScript engine has to implement it as described.
It is common to represent algorithms with some kind of graph or flow chart, but since I am very bad at drawing, I present to you this magnificent pseudocode.
1Take two operands, `x` and `y`, as an input23If type of `x` is the same as type of `y`4 If type of `x` is `Undefined`5 return `true`6 If type of `x` is `Null`7 return `true`89 If type of `x` is `Number`10 If `x` is `NaN`11 return `false`12 If `y` is `NaN`13 return `false`14 If `x` is the same as `y`15 return `true`16 If `x` is `-0` and `y` is `+0`17 return `true`18 if `x` is `+0` and `y` is `-0`19 return `true`20 return `false`2122 If the type of `x` is `String`23 If `x` and `y` are exactly the same sequence of characters24 return `true`25 return `false`2627 If the type of `x` is `Boolean`28 If `x` and `y` are both `true`29 return `true`30 If `x` and `y` are both `false`31 return `true`32 return `false`3334 If `x` and `y` refer to the same object35 return `true`3637 return `false`3839If `x` is `null` and `y` is `undefined`40 return `true`41If `x` is `undefined` and `y` is `null`42 return `true`.4344If type of `x` is `Number` and type of `y` is `String`45 Convert `y` to `Number`46 Return the result of the comparison `x == y`47If type of `x` is `String` and type of `y` is `Number`48 Convert `x` to `Number`49 Return the result of the comparison `x == y`5051If type of `x` is `Boolean`52 Convert `x` to `Number`53 Return the result of the comparison `x == y`54If type of `y` is `Boolean`55 Convert `y` to `Number`56 Return the result of the comparison `x == y`5758If type of `x` is either `String` or `Number` and type of `y` is `Object`59 Convert `y` to primitive value60 Return the result of the comparison `x == y`61If type of `x` is `Object` and type of `y` is either `String` or `Number`62 Convert `x` to primitive value63 Return the result of the comparison `x == y`6465Return `false`
Keep in mind that some parts of the algorithm are missing in pseudocode representation. If you want to see detailed specifications, check it out on ECMAScript website.
Yes, I know, it's just a bunch of if statements and conversions. This conversion part is really what's important. You see, when types are the same, ==
behaves exactly the same as the ===
operator. Important difference is that ==
enforces coercion when types are different.
Coercion is a fancy JavaScript term used when we talk about converting from one value type to another, like from string to number. Coercion plays an important role in ==
comparison.
If you don't want to remember the whole algorithm, think about it in this way. If operands do not have the same type, they are converted to Number
. When types are the same, operands are compared by value.
Object to primitive
One more important thing, before we focus on some examples, is conversion of the Object
type values to primitive values.
In JavaScript, there are 7 primitive values: string, number, bigint, boolean, undefined, null and symbol.
Value with the type of Object
can automatically be converted to primitive value by ==
operator. This conversion will happen when the type of one operand is Object
while the other operand has one of primitive types.
When converting Object
to primitive value, JavaScript will do a couple of things. It will first try to call the built-in prototype method valueOf
. If valueOf
does not return a primitive value, the built-in prototype method toString
will be called next.
As almost anything in JavaScript this behavior has a couple of edge cases and it can be modified and overridden, but you don't have to worry about it for now.
Examples
Still remember that code sample from the beginning? Let's cover it line by line, now when we know how the algorithm works.
I will make one assumption here. Operand on the left side of ==
will be called x
and the operand on the right side will be called y
. With that out of the way, let's get started.
Keep in mind, if operands do not have the same type, ==
will enforce type conversion as long as operands have different types. Only then will operands be compared by value.
Sample 01
'' == '0' // Result: false
This one is pretty simple. Both operands have the same type, String
. This means that operands will be compared by value. Since values are not the same, the result is false
.
Look at IsLooselyEqual algorithm on line: 3, 22 - 24
.
Sample 02
0 == '' // Result: true
Type of operand x
is Number
and type of operand y
is String
. Types are not the same and y
will be converted to Number
.
Empty string ''
converted to Number
, will be 0
. Test it by running Number('')
in your browser's console.
When conversion is completed operands will be compared by value. Since values are the same, 0
is indeed equal to 0
, result will be true
.
Look at IsLooselyEqual algorithm on line: 44 - 46
.
Sample 03
0 == '0' // Result: true
Type of operand x
is Number
and the type of operand y
is String
. Operand y
will be converted to Number
.
When '0'
is converted to Number
, its value is going to be 0
. Test it by running Number('0')
in your browser's console.
When conversion is completed operands will be compared by value. Since values are the same, 0
is indeed equal to 0
, result will be true
.
Look at IsLooselyEqual algorithm on line: 44 - 46
.
Sample 04
false == undefined // Result: falsefalse == null // Result: falsenull == undefined // Result: true
I grouped these examples together since the reasoning for this behavior, in all of them, is pretty much the same.
According to the algorithm, undefined
can only be equal to undefined
or null
. Same is for null
, it can only be equal to null
or undefined
. Because of this, comparing them to false
will always result in false
.
Look at IsLooselyEqual algorithm on line: 3 - 7, 39 - 42, 65
.
Sample 05
[] == 0 // Result: true
Operand x
is the type of Object
and the type of operand y
is String
. As we already know, before they can be compared by value, type conversion is going to happen. In this case, operand x
will be converted to a primitive value. This means Object
to primitive conversion will kick in.
So, let's see how the conversion of []
to primitive value looks like. Method valueOf
is going to be called on []
directly. Test it by running [].valueOf()
in your browser's console. The result of this operation will be []
.
As you can see, we still have []
which is not a primitive value, so method toString()
gets called. Test it by running [].toString()
in your browser's console. Returned value is ''
which is a primitive value. With this, conversion of Object
to primitive value is finally completed.
But, look closely, operands still have different types, x
is now ''
which is type String
and y
is still 0
which is type Number
. Conversion must continue.
Now, x
will be converted to Number
. We already know, from previous examples, that ''
is going to be 0
after conversion. When conversion is completed operands will be compared by value. Since values are the same, 0
is indeed equal to 0
, result will be true
.
Look at IsLooselyEqual algorithm on line: 61 - 63
.
Sample 06
[] == '0' // Result: false
Operand x
is type of Object
, but operand y
is type of String
. Operand x
, with value []
, will be converted to primitive value. We already know, from Sample 05, the result of this conversion is going to be empty string ''
. After conversion, operand x
will have value ''
and type String
.
Since both operands are now the same type, String
, conversion is completed. When compared by value, ''
is not the same as '0'
, so this comparison is false
.
Look at IsLooselyEqual algorithm on line: 61 - 63
.
Sample 07
['a', 'b'] == 'a,b' // Result: true
Operand x
is type of Object
and operand y
is type of String
. In the conversion step, Object
will be converted to primitive value.
After conversion, operand x
will have value 'a,b'
and type String
. Test it by running ['a', 'b'].toString()
in your browser's console.
Both operands are now types of String
. When compared by value, the result is true
, since values are the same.
Look at IsLooselyEqual algorithm on line: 61 - 63
.
Sample 08
NaN == NaN // Result: false
This one is quite strange and yet simple. Comparing any value with NaN
is always going to result in false
. This is the case even if NaN
is compared to NaN
, which seems quite weird.
Look at IsLooselyEqual algorithm on line: 3, 9 - 13
.
Sample 09
'\n\r\t' == 0 // Result: true
Operands have different types, so conversion is required. Since the type of x
is String
it will be converted to Number
. After conversion, '\n\r\t'
will be 0
. Now, when compared by values, 0
is the same as 0
, so the result is true
.
Look at IsLooselyEqual algorithm on line: 47 - 49
.
If you are wondering how '\n\r\t'
, after conversion, can become 0
- it's because of how conversion to Number
works.
There are some special Single Character Escape Sequences like \n
(new line), \t
(horizontal tab), \v
(vertical tab), \r
(carriage return) that are always going to be 0
when converted to Number
. It does not matter in which order and how many of them are contained inside the same string value.
Conclusion
We all know JavaScript can be weird, but if you look a bit under the hood, things can become much clearer. The same thing is with the Equality ==
operator. On the surface it can be really strange, but when you know the algorithm behind it, things can become much easier to understand.
Either way, I strongly recommend always using the Strict Equality ===
operator. But don't get me wrong, it is still really important to know how ==
works. It is, after all, a JavaScript feature which is always going to be part of the language.