Everything I Knew About Equality Comparisons in JavaScript Was a Lie

A tale of betrayal.

Carrie Coxwell
5 min readDec 20, 2016

We’ve all heard the conventional wisdom before: == checks to see if two values are equal while === checks for type and value. For the most part, this works great. 2 == "2" evaluates to true while 2 === "2" evaluates to false. The vast majority of the time, our trusty equality operators perform exactly as our conventional wisdom would have us believe.

Then one day last week, a tweet by Kyle showed up on my Twitter timeline that rocked my world:

“Coercive equality”? What is this dark magic? Here are the three tweets:

Confused? It’s okay. We’ll work it through together.

We’re taught that === is the workhorse, dutifully checking for equality of value and of type, while lazy ol’ == merely checks for value. Sadly, it turns out that == has been undervalued lo these many years, as it performs type coercion before checking the equality of the operands’ values.

So — you may be asking, “What on Earth is type coercion?!” Let’s chat.

What on Earth is type coercion?

First, let’s take a look at Java. In a strongly typed languages like Java, every variable you use must have an explicitly declared data type:

int counter = 1;
String one = "1";
String carrie = "Carrie";
Dog murphy = new Dog("Murphy");

You might be wondering what this has to do with JavaScript, since we never need to declare types when declaring variables. We can just say
let numOfCats = 4 and move on with our lives. Well, even though we never explicitly declare them, everything still has a type in JavaScript. You can test it out for yourself; open up your browser console and try these out:

let oneString = "1"
typeof(oneString) //returns "string"
let oneNum = 1
typeof(oneNum) //returns "number"
let cat = {name: "Peppercorn", color: "orange"}
typeof(cat) //returns "object"
typeof("Elizabeth") //returns "string"

Even though they are not explicitly declared, these all have types — one of the differences between JavaScript and Java is that in JavaScript, everything happens behind the scenes, while Java demands upfront declarations. What does this have to do with our equality operators? I’m getting there, I promise.

Let’s take a look at Java again. What happens if we want to convert something from one data type to another? Well, because Java requires us to explicitly declare our data types, that means we must also explicitly convert things from one data type to another:

int counter = 1;
String counterAsString = String.valueOf(counter);

Our counterAsString variable now evaluates to "1". We’ve successfully converted an int to a String in Java.

What about type conversion in JavaScript? We can do that explicitly here, too:

(1337).toString() //returns "1337"

However, the JavaScript engine will also attempt to do this behind the scenes in some scenarios, and it turns out that this is the behavior we are seeing when 1 == "1" evaluates to true. The "1" operand on the right side is converted behind the scenes to a number. This implicit attempt at type conversion is what we call type coercion.

We understand type coercion. Now what?

You’re still with me! Awesome! Let’s recap what Kyle’s tweets said above:

  • == prefers to compare numbers whenever possible, so
  • == tries to turn operands into numbers.

Here’s a nifty chart I found over at Mozilla’s MDN docs:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness

First, a note about MDN: if you are a new dev starting out, I’m sure you use StackOverflow a lot (Okay, all devs use StackOverflow a lot!). SO is an invaluable resource, have no doubt. But I also encourage you to dig into the MDN documentation as well. It is a treasure trove of information on just about anything you need to know in the HTML/CSS/JS stack.

OK, moving on: take a look at the chart. It summarizes the behavior of == for all possible data type matchups. What first strikes me as noteworthy is that when both operands have the same data type, == works exactly as === would, checking for equality of value.

What happens when the types differ, though? As you can see in the table, == tries to coerce any non-number operand to a number, and then compares both as === would. You’ll notice that there are also some instances of ToPrimitive() coercion, which I feel is outside the scope of what I’m hoping is a beginner-friendly discussion of JavaScript’s equality operators. Perhaps in another piece we can get into ToPrimitive().

What have we actually learned here?

You can’t get a better summary than Kyle’s:

The correct description is: “== allows coercion in the equality comparison and === disallows coercion.”

Why does this matter?

It matters because if you have an incomplete understanding of how == works, its behavior can seem unpredictable. Here’s an example:

if ("7") {
console.log("'7' is true!")
} else {
console.log("'7' is false!")
}

This prints 7 is true to the console. This must mean that '7' evaluates to true, right? Let’s test that theory:

if ('7' == true) {
console.log("'7' is true once again!")
} else {
console.log("wait ... '7' is false?")
}

At first glance, this seems completely insane; but if you consult our trusty MDN table, we can see that both operands are converted to numbers before comparing them as === would. When true and false are converted to numbers, they are converted to 1 and 0, respectively. Try it out:

Number(true) //returns 1
Number(false) //returns 0

We know that '7' is cast to the number 7, so we can now see that the comparison happening behind the scenes is 7 === 1. Of course that’s not true!

We made it!

Armed with this knowledge, == will no longer be seen as the lazy younger brother of ===. Stand up for ==! It’s doing more work than a lot of folks realize.

For way more information on this topic, check out Chapter 4: Coercion, from Kyle Simpson’s book You Don’t Know JS: Types & Grammar.

Many thanks to my dear friend Susannah Skyer Gupta for lending her eyes (and brain) before publishing!

--

--