DEV Community

Cover image for Understanding equality in JavaScript
Ramo Mujagic
Ramo Mujagic

Posted on • Updated on • Originally published at ramomujagic.com

Understanding equality in JavaScript

We have all been there… You compare two values using ==, receive a totally unexpected result and wonder WT* is going on?! - Just Equality == operator at its finest.

Equality == operator has the following syntax.

x == y
Enter fullscreen mode Exit fullscreen mode

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.

'' == '0'             // Result: false
0 == ''               // Result: true
0 == '0'              // Result: true

false == undefined    // Result: false
false == null         // Result: false
null == undefined     // Result: true

[] == 0               // Result: true
[] == '0'             // Result: false
['a', 'b'] == 'a,b'   // Result: true

NaN == NaN            // Result: false
'\n\r\t' == 0         // Result: true
Enter fullscreen mode Exit fullscreen mode

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.

Take two operands, `x` and `y`, as an input

If type of `x` is the same as type of `y`
  If type of `x` is `Undefined`
    return `true`
  If type of `x` is `Null`
    return `true`

  If type of `x` is `Number`
    If `x` is `NaN`
      return `false`
    If `y` is `NaN`
      return `false`
    If `x` is the same as `y`
      return `true`
    If `x` is `-0` and `y` is `+0`
      return `true`
    if `x` is `+0` and `y` is `-0`
      return `true`
    return `false`

  If the type of `x` is `String`
    If `x` and `y` are exactly the same sequence of characters
      return `true`
    return `false`

  If the type of `x` is `Boolean`
    If `x` and `y` are both `true`
      return `true`
    If `x` and `y` are both `false`
      return `true`
    return `false`

  If `x` and `y` refer to the same object
    return `true`

  return `false`

If `x` is `null` and `y` is `undefined`
  return `true`
If `x` is `undefined` and `y` is `null`
  return `true`.

If type of `x` is `Number` and type of `y` is `String`
  Convert `y` to `Number`
  Return the result of the comparison `x == y`
If type of `x` is `String` and type of `y` is `Number`
  Convert `x` to `Number`
  Return the result of the comparison `x == y`

If type of `x` is `Boolean`
  Convert `x` to `Number`
  Return the result of the comparison `x == y`
If type of `y` is `Boolean`
  Convert `y` to `Number`
  Return the result of the comparison `x == y`

If type of `x` is either `String` or `Number` and type of `y` is `Object`
  Convert `y` to primitive value
  Return the result of the comparison `x == y`
If type of `x` is `Object` and type of `y` is either `String` or `Number`
  Convert `x` to primitive value
  Return the result of the comparison `x == y`

Return `false`
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Sample 02

0 == ''               // Result: true
Enter fullscreen mode Exit fullscreen mode

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.

Sample 03

0 == '0'              // Result: true
Enter fullscreen mode Exit fullscreen mode

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.

Sample 04

false == undefined    // Result: false
false == null         // Result: false
null == undefined     // Result: true
Enter fullscreen mode Exit fullscreen mode

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.

Sample 05

[] == 0               // Result: true
Enter fullscreen mode Exit fullscreen mode

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.

Sample 06

[] == '0'             // Result: false
Enter fullscreen mode Exit fullscreen mode

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.

Sample 07

['a', 'b'] == 'a,b'   // Result: true
Enter fullscreen mode Exit fullscreen mode

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.

Sample 08

NaN == NaN            // Result: false
Enter fullscreen mode Exit fullscreen mode

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.

Sample 09

'\n\r\t' == 0         // Result: true
Enter fullscreen mode Exit fullscreen mode

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.

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.


Check out other articles on my blog.

Top comments (2)

Collapse
 
jiseeeh_6 profile image
JC Camara

Good read for a beginner like me 👍

Collapse
 
rmmgc profile image
Ramo Mujagic

Thanks, glad you liked it 😄