JavaScript Interview Prep
Type System & Coercion

== vs ===

The Strict vs Lenient Librarian

LinkedIn Hook

[] == false is true.

[] == ![] is also true.

But if ([]) runs the truthy branch — because [] is truthy.

How can [] be "equal to false" and "truthy" at the same time? The answer is the == algorithm, spelled out line by line in the ECMAScript spec.

Once you learn the 5 coercion steps == runs, every trick question becomes a mechanical walkthrough. And once you know the one legitimate reason to still use == in modern code, you stop memorizing and start reasoning.

In this lesson you'll learn the exact difference between == and ===, walk through the algorithm on real examples, decode [] == ![] step by step, and see a full comparison table of common coercions.

If an interviewer ever smirks and writes [] == ![] on the whiteboard — this lesson is your cheat code.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #EqualityOperators #TypeCoercion #Frontend #CodingInterview #WebDevelopment


What You'll Learn

  • The mechanical difference between === (strict) and == (abstract) equality
  • The 5 coercion rules == applies, in order, from the ECMAScript spec
  • Step-by-step walkthroughs of [] == false and [] == ![], plus the one valid modern use of ==

Strict vs Lenient Librarian

Think of === as a strict librarian who checks both your name AND your ID photo. If either doesn't match, you're rejected. == is the lenient librarian who says "close enough" — if your name sounds similar, she'll let you through after checking a conversion chart.

Strict Equality (===)

=== checks type AND value. No coercion, no surprises:

5 === 5; // true -- same type, same value
5 === "5"; // false -- different types, stop immediately
true === 1; // false -- boolean !== number
null === undefined; // false -- different types
NaN === NaN; // false -- NaN is not equal to anything, including itself!

Abstract Equality (==) — The Coercion Algorithm

== follows a specific algorithm from the ECMAScript spec. Here are the key rules:

Step 1: If types are the same       -> behave like ===
Step 2: null == undefined           -> true (special rule)
Step 3: If comparing number & string -> convert string to number
Step 4: If one side is boolean       -> convert boolean to number first
Step 5: If one side is object & other is primitive -> ToPrimitive(object)

Walking Through the Algorithm

// Example 1: "5" == 5
// Types: string vs number
// Rule: convert string to number -> Number("5") = 5
// Now: 5 == 5 -> true
"5" == 5; // true

// Example 2: true == 1
// Types: boolean vs number
// Rule: convert boolean to number -> Number(true) = 1
// Now: 1 == 1 -> true
true == 1; // true

// Example 3: true == "1"
// Types: boolean vs string
// Rule: convert boolean to number -> Number(true) = 1
// Now: 1 == "1"
// Rule: convert string to number -> Number("1") = 1
// Now: 1 == 1 -> true
true == "1"; // true

// Example 4: null == undefined
// Special rule: null and undefined are == to each other and NOTHING else
null == undefined; // true
null == 0; // false
null == ""; // false
null == false; // false
undefined == 0; // false
undefined == ""; // false

The Notorious [] == false and [] == ![]

// Why [] == false is true:
// Step 1: boolean to number -> false becomes 0
// Now: [] == 0
// Step 2: ToPrimitive([]) -> [].toString() -> ""
// Now: "" == 0
// Step 3: string to number -> Number("") -> 0
// Now: 0 == 0 -> true
[] == false; // true

// Why [] == ![] is true:
// Step 0: ![] evaluates first. [] is truthy, so ![] = false
// Now: [] == false
// (same steps as above)
// Result: true
[] == ![]; // true

// Why this is insane -- both sides are "equal" to false,
// but the array itself is truthy:
if ([]) console.log("truthy!"); // "truthy!" -- [] IS truthy
if ([] == false) console.log("wat"); // "wat" -- but == says it equals false

Common == Comparison Table

```text title="Common == Comparison Table" +-----------------------+---------+----------+ | Comparison | == | === | +-----------------------+---------+----------+ | 5 == "5" | true | false | | 0 == "" | true | false | | 0 == false | true | false | | "" == false | true | false | | null == undefined | true | false | | null == 0 | false | false | | NaN == NaN | false | false | | [] == false | true | false | | [] == ![] | true | false | | "" == [] | true | false | | 0 == [] | true | false | | false == "0" | true | false | | false == "" | true | false | +-----------------------+---------+----------+


![== vs === visual 1](./images/equality-operators-visual-1.png)

---

## Common Mistakes

- Using `==` "because it's shorter" — the coercion algorithm has at least five branches and leads to bugs that `===` simply can't produce.
- Assuming `null == 0` or `undefined == 0` — both are `false`. `null` and `undefined` are only `==` to each other, nothing else.
- Forgetting the `NaN` rule when debugging — `NaN == NaN`, `NaN === NaN`, `NaN == anything` are all `false`. Use `Number.isNaN(x)` to check.

---

## Interview Questions

**Q: Explain the difference between `==` and `===`.**
> `===` (strict equality) checks both type and value with no coercion. `==` (abstract equality) allows type coercion before comparison -- it follows a specific algorithm that converts types to match before comparing values.

**Q: Why does `[] == ![]` return `true`?**
> First, `![]` evaluates to `false` (since `[]` is truthy). So it becomes `[] == false`. The boolean `false` converts to `0`. Then `ToPrimitive([])` produces `""`. Then `Number("")` is `0`. So `0 == 0` is `true`.

**Q: Is there any case where `==` is useful?**
> The most legitimate use is `value == null`, which checks for both `null` and `undefined` in a single comparison. This is commonly accepted even in strict codebases. Beyond that, `===` is preferred for clarity.

**Q: What does `null == undefined` return? What about `===`?**
> `null == undefined` is `true` because the spec has a special rule that `null` and `undefined` are loosely equal to each other (and to nothing else). `null === undefined` is `false` because they are distinct types.

**Q: Walk through why `[] == ![]` is `true`.**
> `![]` is evaluated first. `[]` is truthy, so `![]` is `false`. The expression becomes `[] == false`. The `==` algorithm converts `false` to `0`, giving `[] == 0`. `ToPrimitive([])` returns `""`. Now `"" == 0`. The string is converted to a number: `Number("")` is `0`. Finally `0 == 0` is `true`.

---

## Quick Reference — Cheat Sheet

```text title="Quick Map"
== vs === -- QUICK MAP

===  strict equality
  - no coercion
  - checks type FIRST, then value
  - NaN === NaN is false

==   abstract equality (coercion algorithm)
  1. same types       -> act like ===
  2. null == undefined -> true (special)
  3. number vs string -> string -> number
  4. boolean side     -> boolean -> number first
  5. object vs prim   -> ToPrimitive(object)

Rule of thumb:
  default: use ===
  exception: value == null  (checks null AND undefined)

Famous traps:
  [] == false   -> true   (but [] is truthy!)
  [] == ![]     -> true
  0 == "0"      -> true
  NaN == NaN    -> false
  null == 0     -> false  (null only == undefined)

Previous: Type Coercion -> The Overly Helpful Translator Next: Truthy / Falsy -> The Nightclub Bouncer


This is Lesson 8.3 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page