The Double Equals Operator in JavaScript

After reading this article, you will know all you need to know about coercion and double equals in JavaScript.

The double equals (loose equality) operator is an interesting operator. Many avoid it because they don't know how it works. But that's not a good reason to avoid it, a better reason would be knowing how it works, and knowing why you want to avoid it. And then you know why you prefer the triple equals (===) operator for comparisons. Let's get started!

Double Equals

The double equals operator coerce the operands if their types are not the same, otherwise, it will use the triple equals operator. So, 2 == 3 is the same as 2 === 3 because both 2 and 3 are of the number type. But if the types are not the same, JavaScript will try to coerce either one or both of the operands. For example, if you compare 2 == "3", you are comparing number 2 with a string whose value is 3. In this case, JavaScript will coerce string 3 to number 3, and then it will call 2 === 3 which will result false.

Now, that we know the basics of the double equals operator, let's dive deeper and learn how the operator decides what to do.

The Abstract Equality Comparison

Assume that we are comparing x and y, where x and y could be of any type:

Start: Check if the types are the same. If the types are the same, JavaScript will call triple equals and you are done.

but, if the types are not the same, JavaScript will follow another path. In this path, JavaScript will try to figure out what to coerce. These are the steps it will execute in order:

a. First check if we are comparing null and undefined. If we are, return true. If not:

b. Check if we are comparing a string with a number. If we are, coerce the string value to number and call the double equals and start over again back to the very first step. Eg:

2 == "3"  
      ↓   coerce "3" to number 3.
2 ==  3   calling double equals again, and start over  
...

but if we are not:

c. Check if we are comparing a boolean with something else. If we are, coerce the boolean to a number and call double equals and start over. eg:

true == "3"  
↓             coerce true to number 1.
1    == "3"   start over again.  
...

but if we are not:

d. Check if we are comparing an object with a number, string, or a symbol. If we are, coerce the object to a primitive, call double equals on the result and start over again. Eg:

{ a: 'hello'}      ==     "5"
     ↓                          coerce the object to a primitive
"[object Object]"  ==     "5"   and start over again
...

another example:

[1,2,3]     ==     "5"
  ↓                       coerce object to a primitive
"1,2,3"     ==     "5"    and start over again
...

(I'll explain how the objects get coerced to primitives in a second, but for now bare with me.)

If not, we don't have anything else to check. If none of the steps above matches, return false at the end.

Here is the complete picture:

You can click the image to see the larger version.

Abstract Operators:

Coercing Objects to Primitives

I promised you to explain to you how objects get coerced to primitives. This is how it works:

First by default, call the valueOf method of the object. If valueOf returns a primitive, use that.

Otherwise call the toString method of the object. If toString returns a primitive, use that

otherwise throw an error.

Note 1: These steps happen by default. If it is hinted to prefer string, JavaScript will call toString first, and then valueOf. By default JavaScript is hinted to prefer number, so it will call valueOf first, and then toString.

Note 2: When coercing a Date object to a primitive, JavaScript is hinted to prefer string. So it will call toString first and then valueOf. In all the other cases, the default preferred type hint is number.

Let's look at some examples:

Example 1:

// coercing an empty object to a primitive
var x = {};  
x.valueOf();   // -> {} : not a primitive, call toString now.  
x.toString();  // -> "[object Object]": string is a primitive, I like it!  

In the example above, when x is coerced to a primitive, the result would be the funny looking string: "[object Object]"

Example 2:

// coercing an object with custom `valueOf`
var x = {name: 'Tom'};  
x.valueOf = function () {return 5;};  
x.valueOf();   // -> 5 : number primitive, I liked it!  

In the example above, when x is coerced to a primitive, the result would be the number 5.

Example 3:

// coercing an array of numbers to a primitive
var x = [1,2,3];  
x.valueOf();   // -> [1,2,3] : not a primitive, call toString now.  
x.toString();  // -> "1,2,3" : string is a primitive, I like it!  

In the example above, when x is coerced to a primitive, the result would be the string: "1,2,3"

Example 4:

// coercing an array of elements with different types to a primitive
var x = [undefined, null, true, 1, "5", new Date(), {a: 2}, [1,2,3]]  
x.valueOf();   // -> Array itself : not a primitive, call toString now.  
x.toString();  // -> ",,true,1,5,Sat Mar 05 2016 10:59:47 GMT-0500 (EST),[object Object],1,2,3" : string is a primitive, I like it!  

In the example above, when x is coerced to a primitive, the result would be the string: ",,true,1,5,Sat Mar 05 2016 10:59:47 GMT-0500 (EST),[object Object],1,2,3"

Coercing to number: ToNumber(input)

The following summarizes what ToNumber does for different input types:

  • undefinedNaN
  • null0
  • number → return the number itself
  • boolean0 if input is false, 1 if input is true
  • string → parse string, if string is a number return number, otherwise return NaN. Empty string returns 0.

    Eg: "5"5, "" → 0, "29xY"NaN

  • object: call ToPrimitive first, then call ToNumber on the result of ToPrimitive. See the toPrimitive section to learn how objects get coerced to primitives Date → calls getTime() method and returns the value

Coercing to string: ToString(input)

The following summarizes what ToNumber does for different input types:

  • undefined"undefined"
  • null"null"
  • number → "number"
  • boolean → "true", "false"
  • string → return the string itself.
  • object: call ToPrimitive first with a hint of string, then call ToString on the result of ToPrimitive. See the toPrimitive section to learn how objects get coerced to primitives Date → calls toString() method and returns the value

Coercing to boolean: ToBoolean(input)

In JavaScript, the following values are falsy:

  • undefined
  • null
  • 0
  • ""
  • NaN

Everything else is truthy. Knowing this fact will make it easy to know what happens after a value is coerced to a boolean:

  • undefinedfalse
  • nullfalse
  • numberfalse if 0, otherwise, true
  • boolean → input iteself
  • stringfalse if empty string "", otherwise true
  • objecttrue. This follows that arraytrue
  • Datetrue

Double Equals Examples

Now that we know how double equals work, we can have some fun and look at some examples. I am going to explain what path does JavaScript take to decide what to output.

Example: [] == 0

a. Are types different? yes, so we have to take the long path:
b. Are we comparing null with undefined? no, go to next check:
c. Are we comparing a number with a string? no, go to next check:
d. Are we comparing a boolean with something else? no, go to the next check:

e. Are we comparing an object with a number or string, or symbol? YES! we are comparing the array object with a number. Ok, let's coerce the array to a primitive and call double equals again:

  • calling valueOf on the array: output is [], which is not a primitive and I can't use it. Let's call toString:
  • calling toString on the array: output is "". Perfect it is a primitive string, and I can use it.

Calling double equals again with the result of coercing the object to a primitive:

"" == 0

And now we go back to the beginning and start over again:

a. Are the types the same? no, take the long path:
b. Are we comparing null with undefined? no, go to next check:
c. Are we comparing a number with a string? YES, now let's coerce the string to a number. coercing the empty string to a number will return 0. Call double equals again and start over:

0 == 0

a. Are the types the same? Yes, call triple equals 0 === 0 and the result is true

So in summary:

[] == 0
↓
"" == 0
↓
0 == 0  
↓
0 === 0  
→ true

Let's keep this example in mind and look at some other examples. In the following examples, I will it to you to go through the whole algorithm.

Example: [] == "0"

[] == "0"
↓
"" == "0"
↓
"" === "0"
→ false

Example: false == 1

false == 1  
↓
0 == 1  
↓
0 === 1  
→ false

Example: {} == false

{} == false
↓
{} == 0
↓
"[object Object]" == 0
↓
NaN == 0  
↓
NaN === 0  
→ false

Example: undefined == false

undefined == false  
↓
undefined == 0  
→ false

Example: [1,2,3] == 123:

[1,2,3] == 123
↓
"1,2,3" == 123
↓
NaN    ==  123  
↓
NaN   ===  123  
↓
→ false

Example: [123] == 123:

[123] == 123
↓
"123" == 123
↓
123    ==  123  
↓
123   ===  123  
↓
→ true

Example: [123] == "123":

[123] == "123"
↓
"123" == "123"
↓
"123" === "123"
↓
→ true

For the following examples, x is defined as the following:

var x = {  
  a: '...',
  toString: function () {
    return false;
  },
  valueOf: function () {
    return new Boolean(true);
  }
};

Example: x == "0":

x == "0"  
↓
false == "0"  
↓
0    ==  "0"  
↓
0    ==  0  
↓
0   === 0  
↓
→ true

Example: x == 5:

x == 5  
↓
false == 5  
↓
0    ==  5  
↓
0   ===  5  
→ false

Example: x == [1,2,3]:

x == [1,2,3] // both are objects  
↓
x === [1,2,3] // they are stored at different addresses  
→ false // the address is not the same