How The Abstract Equality Comparison (==) In JavaScript Works

Subscribe to my newsletter and never miss my upcoming articles

The Abstract Equality Comparison Operator, a.k.a. == in JavaScript is the so-called type-coercing equality check. It coerces types if necessary and can lead to some pretty weird results.

But we can make sense of all this, and you'll see that you can follow along.

There is an algorithm behind it

Whenever you use the == operator, there is actually an algorithm behind it that determines the result of the comparison. This algorithm has its place within the ECMA spec (the spec behind JavaScript) and can be found in chapter 7.2.15.

It's actually pretty lengthy and takes a lot of space, but it covers all possible inputs and provides a concise way to determine the output of any comparison. You can see an excerpt from it below which I processed a little so you can read it better.

The Algorithm

  1. If Type(x) is the same as Type(y), then
    1. If Type(x) is Number OR BigInt, then
      1. If x is NaN, return false.
      2. If y is NaN, return false.
      3. If x has the same value as y, return true.
      4. If x is +0 and y is -0, return true.
      5. If x is -0 and y is +0, return true.
      6. Return false.
    2. If Type(x) is Undefined, return true.
    3. If Type(x) is Null, return true.
    4. If Type(x) is String, then
      1. if x and y are exactly the same sequence of characters (same length, same characters in sequence), then
        1. return true.
      2. return false.
    5. If Type(x) is Boolean, then
      1. If x is true and y is true, then
        1. return true.
      2. if x is false and y is false, then
        1. return true.
      3. return false.
    6. If Type(x) is Symbol, then
      1. If x and y are both the same Symbol value, then
        1. return true.
      2. return false.
    7. If x and y are the same Object value, then
      1. return true.
    8. return false.
  2. If x is null and y is undefined, return true.
  3. If x is undefined and y is null, return true.
  4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
  6. If Type(x) is BigInt and Type(y) is String, then
    1. Set n to StringToBigInt(y).
    2. If n is NaN, return false.
    3. Return the result of the comparison x == n.
  7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x.
  8. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  9. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
  10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result of the comparison ToPrimitive(x) == y.
  12. If Type(x) is BigInt and Type(y) is Number, OR if Type(x) is Number and Type(y) is BigInt, then
    1. If x or y are any of NaN, +INFINITY, or -INFINITY, return false.
    2. If the mathematical value of x is equal to the mathematical value of y, then
      1. return true.
    3. return false.
  13. Return false.

All of this may seem a little intimidating, but that's okay. You'll see that there is enough logic to it to be understandable. But before we go in, you first have to learn about some functions that are referenced within this algorithm.

Supporting Functions

Type(x)

This is not the typeof operator, but a runtime function that returns exactly the type of a value at hand.

Type(null) is actually Null, for example, and not object.

ToNumber

This is also a runtime function. It basically works the same as calling Number(x).

StringToBigInt

This is basically ToNumber with a few additions which we won't cover now.

ToPrimitive

This is the runtime function to convert any complex object into a primitive. There is once again a whole algorithm to it and it goes as follows.

  1. If Type(input) is Object, then
    1. If preferredType is not present, set hint to "default"
    2. Else If preferredType is hint String, set hint to "string"
    3. Else
      1. Set hint to "number"
    4. Set exoticToPrim to input[Symbol.iterator]
    5. If exoticToPrim is not undefined, then
      1. Set result to exoticToPrim(hint)
      2. If Type(result) is not Object, return result
      3. Throw a TypeError exception
    6. If hint is "default", set hint to "number"
      1. If hint is "string", then
        1. Set methodNames to ["toString", "valueOf"]
      2. Else
        1. Set methodNames to ["valueOf", "toString"]
      3. For each name in list methodNames, in order, do
        1. If input[name] exists (not undefined), then
        2. Set result to input[name]()
        3. If Type(result) is not Object, return result
      4. Throw a TypeError exception
  2. Return input

Making Sense Of It With Examples

You now have 'a lot of algorithm' at hand, but it may still be pretty difficult to actually apply that knowledge. This is where examples come in. They usually help a lot in understanding more complex things.

Example 1

Let's start with 1 == "1" as a light entry.

1 == "1";
// =>
// Step 4 applies, x is Number and y is string.
// So it is evaluated as
1 == Number("1");
// Number("1") yields 1, so we're at
1 == 1;
// which is
true;

Example 2

Let's continue our examples with true == 0.

true == 0;
// =>
// Step 9 applies, as x is a boolean.
// It is evaluated as
Number(true) == 0;
// Number(true) yields 1, so we're at
1 == 0;
// which is
false;

Example 3

Let's go one step further and use one object in our comparison, so we'll go with "" == [].

"" == [];
// =>
// Step 10 applies, as x is a string and y is an object.
// [] needs to be converted into a primitive.
// [][Symbol.toPrimitive] is undefined so that doesn't help.
// The type hint is "default", and thus "number" is passed, according to the primitive
// conversion algorithm
// The methods now used are [valueOf, toString].
// [].valueOf() returns [] again, so once again no help.
// [].toString() yields "" which is a primitive, so the algorithm terminates successfully.
"" == "";
// according to step 1.4.1 yields
true;

Example 4

Let's try [] == ![] now. It's one of the weirder comparisons which usually makes people raise at least an eyebrow.

[] == ![];
// ![] is evaluated first and is no actual part of the comparison.
// ![] is a boolean conversion together with negation, so you could also
// read it as
[] == !Boolean([]);
// Boolean([]) yields true, as all objects do
[] == !true;
// and that negated is of course
[] == false;

// Now step 9 of the algorithm applies, as y is a boolean
[] == Number(false);
// Number(false) yields 0
[] == 0;
// And the algorithm calls itself again where this time, step 11 applies, as x is an object and y is a number

ToPrimitive([]) == 0;
// [][Symbol.toPrimitive] is undefined so that doesn't help.
// The type hint is still at "default" so it gets replaced with "number" according to step 1.6 of the primitive conversion algorithm.
// The methods used are now [valueOf, toString].
// [].valueOf() yields [] again which doesn't help.
// [].toString() however results in "", an empty string.

"" == 0;
// The algorithm is once again calling itself where this time step 5 applies, as
// x is a string and y is a number.

Number("") == 0;
// Number("") results in 0 and the algorithm calls itself once again.

0 == 0;
// This time step 1.1.3 applies, as both values have type number.
// Because both sides have the exact same value, the result can only be.
true;

What's next?

You can come up with your own examples now. Simply follow the algorithms and you'll surely get the right answer every time. But you can of course ensure that you are right by simply logging the expression in your browser's dev tool and then compare your result against what your browser says is right.

With a little more practice, you'll surely always know the answer to some JavaScript quizzes in the future!

Before you leave

If you like my content, visit me on Twitter, and perhaps you’ll like what you see.

No Comments Yet