Skip to content
academia.sh

Lesson 09 / 21

Equality Comparisons

Strict equality, the step-by-step algorithm of loose equality, same-value equality, and identity comparison for objects.

Contents

The previous lesson defined the coercion procedures: how a value is converted to number, to string, and to a primitive value. This lesson takes up the place those procedures are used the most. JavaScript has three separate ways to compare two values, and the three answer different questions.

All three diverge on a single axis: whether coercion is applied before comparison, and how the special number values (NaN, -0) are handled. Knowing the distinction turns the question “why did this come out equal” from a guess into a traceable step.

Strict Equality

The === operator looks at type first. If the types differ, the result is directly false; no coercion is done. If the types are the same, the values are compared.

console.log(21.5 === 21.5);
console.log("A1" === "A1");
console.log(1 === "1");
console.log(NaN === NaN);
console.log(0 === -0);
true
true
false
false
true

The third line gives false because of the type difference. The last two lines are two exceptions specific to the number type, and both come from the IEEE 754 representation. NaN is not equal to any value, including itself; the standard writes this explicitly. Signed zeros, despite being different bit patterns, are considered equal.

NaN not being equal to itself makes it impossible to test whether a value is NaN using equality. There is a separate probe for this:

console.log(Number.isNaN(NaN));
console.log(Number.isNaN("abc"));
console.log(isNaN("abc"));
true
false
true

The difference between the two probes matters. Number.isNaN requires the value to be of number type and be NaN. The general probe first converts its argument to a number; because the text "abc" converts to NaN, it gives true. The second answers the question “can this value not be converted to a number,” the first answers “is this value NaN.” Only the first will be used in this course.

Loose Equality

The == operator, if the types differ, applies coercion and descends to a common type. Its steps are defined in sequence in the standard, and in practice the following five rules are enough:

  1. If the types are the same, strict equality is applied.
  2. If one side is null and the other is undefined, the result is true. Neither of these is loosely equal to any other value.
  3. If one side is a number and the other is a string, the string is converted to a number.
  4. If one side is a boolean, that side is converted to a number first, then the rules are applied again.
  5. If one side is an object and the other is primitive, the object is converted to a primitive value and the rules are applied again.

Results of the rules:

console.log(null == undefined);
console.log(null == 0, undefined == 0);
console.log("1" == 1);
console.log("" == 0);
console.log("0" == 0);
console.log("" == "0");
true
false false
true
true
true
false

The second line is a direct consequence of the second rule: null is not converted to a number; it only matches undefined. This is the one behavior that makes it safe to write a null check with loose equality.

The boolean rule produces the most counterintuitive results:

console.log(true == 1, true == "1", false == "");
console.log([] == false);
console.log([21.5] == 21.5);
console.log([1, 2] == "1,2");
true true true
true
true
true

Reading the fourth line step by step shows the method. In the expression [] == false, the fourth rule applies first: false is converted to a number, becoming 0. Now one side is an object, the other a number; per the fifth rule the array is converted to a primitive value and becomes an empty string. In the last step, the third rule converts the empty string to a number; the result becomes 0 == 0 and comes out true. No step is arbitrary.

Loose Equality Is Not Transitive

One of the properties expected of an equality relation is transitivity: if a=ba = b and b=cb = c, then a=ca = c should hold. Loose equality does not carry this property:

console.log("0" == 0);
console.log(0 == "");
console.log("0" == "");
true
true
false

In the first two lines, the comparison was done through number; in the third line, because both sides are strings, the first rule kicks in and there is no coercion. The result is a relation that does not contradict itself but is not transitive.

The course’s rule follows from this:

Equality comparisons are written with ===. The one exception is the value == null form, which tests together whether a value is null or undefined.

The reasoning for the exception is the second rule: this form covers exactly two values, and accidentally includes no other value.

const incoming = { station: "A2", humidity: undefined };
console.log("loose:", incoming.humidity == null);
console.log("strict:", incoming.humidity === null);
loose: true
strict: false

The two lines answer different questions. The first is “does this field have no value at all”; the second is “is this field deliberately empty.” The distinction in the Primitive Types lesson determines which question needs to be asked.

Same-Value Equality

The third way removes both of strict equality’s exceptions:

console.log(Object.is(NaN, NaN));
console.log(Object.is(0, -0));
console.log(Object.is(21.5, 21.5));
true
false
true

Object.is asks whether two values are the same value. NaN is the same value as itself; 0 and -0 are different values. This is the relation used internally in several places in the standard.

Object.is is not needed in everyday use; === together with Number.isNaN does the same job and is more readable. Knowing the distinction is necessary to explain the behavior of built-in methods — for example, some array search methods can find NaN while others cannot, because they use different relations.

Comparing Objects

None of the three operators compares object content. What is compared for objects is identity: whether two references point to the same object.

const a = { station: "A1" };
const b = { station: "A1" };
const c = a;
console.log(a === b);
console.log(a === c);
console.log(a == b);
false
true
false

a and b have the same content but are different objects. c is equal because it points to the same object as a. Loose equality gives the same result: because both sides are objects, the first rule kicks in and no coercion happens.

The identity–equality distinction established in the Variables and Binding lesson of the Programming Fundamentals course maps directly onto the language’s operators here. If content comparison is needed, it has to be written; the language does not provide it.

Application to the Measurement Script

Checks that test records’ validity, when written with the right operator, distinguish both missing and corrupted fields:

const records = [
  { station: "A1", temperature: 21.5, humidity: null },
  { station: "A2", temperature: 0, humidity: 55 },
  { station: "B1", temperature: NaN, humidity: 41 },
];

for (const record of records) {
  const humidityMissing = record.humidity == null;
  const temperatureInvalid = Number.isNaN(record.temperature);
  console.log(record.station, "humidity missing:", humidityMissing, "temperature invalid:", temperatureInvalid);
}
A1 humidity missing: true temperature invalid: false
A2 humidity missing: false temperature invalid: false
B1 humidity missing: false temperature invalid: true

The 0 value in the second record passed both checks — zero is neither missing nor invalid. This is exactly the situation the shorthand form broke in the previous lesson. The NaN in the third record can only be caught with Number.isNaN; the form record.temperature === NaN would never give true.

Summary

  • === looks at type first; if the types differ, it gives false without coercion.
  • NaN is not equal to any value, not even itself; 0 and -0 are equal under strict equality.
  • == descends to a common type with five rules; null and undefined are equal only to each other, booleans are converted to number first, objects are reduced to a primitive value.
  • Loose equality is not transitive; this is why comparisons are written with ===, with the one exception being the value == null form.
  • Object.is is same-value equality: NaN equals itself, 0 and -0 differ.
  • Objects are compared by identity; content comparison does not happen unless written.

Next Step

The question left from the previous lesson is still open: what happens when a number, string, or object is placed inside a condition? The form if (value) is not an equality comparison; it calls the third coercion procedure, the one that converts a value to boolean. The next lesson will give this procedure’s full list and show why the value 0 is mistaken for missing data.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close