JavaScript fundamentals

== vs === in JavaScript

JavaScript has two equality operators. The double equals == (loose or abstract equality) silently converts types before comparing. The triple equals === (strict equality) requires the same type and the same value — no conversion, no surprises. Understanding the difference is essential for reading code reviews, avoiding subtle bugs, and talking confidently with other engineers.

TL;DR

  • == (abstract / loose equality) — converts operands to a common type before comparing. The conversion rules are complex and produce non-obvious results.
  • === (strict equality) — requires both operands to have the same type and the same value. No coercion occurs.
  • Default rule: always use ===. ESLint's eqeqeq rule enforces this on most professional teams.
  • One accepted exception: value == null checks for both null and undefined simultaneously — a common, deliberate idiom.
  • NaN quirk: NaN === NaN is false. Use Number.isNaN() instead.

Side-by-side comparison

Aspect == (abstract equality) === (strict equality)
Type coercion Yes — converts types before comparing No — different types always return false
Algorithm Abstract Equality Comparison (ECMAScript spec §7.2.14) Strict Equality Comparison (ECMAScript spec §7.2.16)
Performance Marginally slower (coercion overhead) Marginally faster (no coercion)
Predictability Surprising results common; hard to reason about Explicit and easy to reason about
ESLint default Flagged by the eqeqeq rule Required by the eqeqeq rule
null == undefined true false
0 == false true false
"" == false true false
[] == false true false
Recommended use Only value == null (null/undefined check) All other comparisons

What is == (abstract equality)?

The == operator is defined by the Abstract Equality Comparison algorithm in the ECMAScript specification. Before deciding whether two values are equal, JavaScript inspects their types. If the types differ, it applies a series of conversion rules:

  • If one operand is a boolean, convert it to a number first (true → 1, false → 0), then re-run the comparison.
  • If one operand is a string and the other is a number, convert the string to a number, then compare.
  • If one operand is an object and the other is a primitive, call the object's valueOf() or toString() to obtain a primitive, then compare.
  • null == undefined is true — the spec defines this explicitly.
  • null == anything_else is always false.

The result is a cascade of conversions that most developers cannot recite from memory. That is precisely why == is considered dangerous in everyday code: the conversion rules are an expert-level detail, not something a reader should need to recall to understand a simple conditional.

What is === (strict equality)?

The === operator uses the Strict Equality Comparison algorithm. The rules are simple:

  • If the two operands have different types, the result is false. No conversion.
  • If both operands are numbers, standard numeric equality applies — except NaN !== NaN (the only value in JavaScript not equal to itself).
  • If both are strings, they are equal when they have the same length and the same characters in every position.
  • If both are booleans, both must be true or both must be false.
  • If both are objects (including arrays and functions), equality is reference equality — they are equal only if they are the same object in memory.
  • null === null and undefined === undefined are both true.

Note: strict equality is related to but distinct from the SameValueZero algorithm used by Map, Set, and Array.prototype.includes. SameValueZero differs in that it treats NaN as equal to NaN, and considers +0 equal to -0.

Code side-by-side

Common coercion gotchas with == and how === avoids each one:

== — surprising coercions

{`// Boolean coercion
0 == false        // true  ⚠️
1 == true         // true  ⚠️

// String/number coercion
'1' == 1          // true  ⚠️
'' == 0           // true  ⚠️
'0' == false      // true  ⚠️

// Object coercion
[] == false       // true  ⚠️
[] == ![]         // true  ⚠️ (infamous)
[1] == 1          // true  ⚠️

// null / undefined
null == undefined // true
null == false     // false
null == 0         // false

// NaN — same with both
NaN == NaN        // false ⚠️`}

=== — no surprises

{`// Types differ → always false
0 === false       // false ✓
1 === true        // false ✓

'1' === 1         // false ✓
'' === 0          // false ✓
'0' === false     // false ✓

[] === false      // false ✓
[] === ![]        // false ✓
[1] === 1         // false ✓

null === undefined // false ✓
null === null      // true  ✓
undefined === undefined // true ✓

// NaN still not equal to itself
NaN === NaN       // false ⚠️
// Use: Number.isNaN(value) → true`}

The ESLint eqeqeq rule

Most professional JavaScript and TypeScript projects enable the eqeqeq ESLint rule. It reports any use of == or != as an error, prompting the developer to use === or !== instead.

The rule has three modes:

  • "always" — bans == in every context. Even == null is disallowed; you must write the full === null || === undefined check.
  • "smart" — permits == null and == typeof comparisons, bans everything else. This is the most common team setting.
  • "allow-null" — deprecated alias for allowing only == null.

When a team member says "the linter is flagging your comparison" or "switch to triple equals, the ESLint rule requires it", they are almost always referring to eqeqeq.

If you are working with TypeScript, note that TypeScript's own compiler will catch some — but not all — coercion-prone comparisons. The eqeqeq rule remains useful even in TypeScript projects.

How engineers talk about == vs ===

These are phrases you will encounter in pull requests, code reviews, and technical interviews:

In code reviews

  • "Use triple equals here — loose equality can cause silent coercion bugs."
  • "This coerces the string to a number before comparing — is that intentional?"
  • "The linter will flag this because of the eqeqeq rule — switch to ===."
  • "== null is fine here — it's the standard idiom for checking both null and undefined at once."

In technical discussions

  • "Abstract equality runs a coercion algorithm that most developers can't recite — that's why we avoid it."
  • "Strict equality is what you want 99% of the time: same type, same value, no surprises."
  • "The classic JS gotcha: [] == false is true under loose equality — never what you intended."
  • "Remember that NaN is not equal to itself — use Number.isNaN(), not an equality check."

In job interviews

  • "Could you explain the difference between abstract and strict equality in JavaScript?"
  • "What does type coercion mean and how does it affect equality comparisons?"
  • "Why do we say [] is falsy under loose equality but not falsy in a boolean context?"

Truthy, falsy, and why they matter here

Truthy and falsy describe how JavaScript treats a value when used in a boolean context (an if statement, a ternary, a logical operator). They are closely related to the coercion behaviour of ==:

  • Falsy values: false, 0, -0, 0n, "", null, undefined, NaN.
  • Everything else is truthy — including [], {}, "0", and "false".

A common mistake is assuming that because [] is falsy under == comparison with false, it is also falsy in a boolean context. It is not: if ([]) { ... } executes the block because [] is a truthy value. This asymmetry is exactly why mixing == comparisons with implicit boolean checks leads to confusing code.

Decision guide

  • Default choice for any comparison → ===
  • Need to check if a value is null or undefinedvalue <strong>==</strong> null is idiomatic and widely accepted
  • Comparing a string input to a number → convert explicitly (Number(str), parseInt(str, 10)), then use ===
  • Checking for NaN → use Number.isNaN(value), not any equality operator
  • ESLint is flagging your == → switch to === or add an eslint-disable-next-line comment with a justification
  • Working in TypeScript → === everywhere; rely on the type system for type safety
  • Checking object/array equality by contents → use a deep-equal library; neither == nor === compares by value for objects
  • Using Map, Set, or Array.prototype.includes → these use SameValueZero, not ===; NaN membership works correctly there

Key vocabulary

  • Abstract equality (==) — JavaScript equality operator that applies type coercion before comparing. Defined by the Abstract Equality Comparison algorithm in the ECMAScript specification.
  • Strict equality (===) — JavaScript equality operator that requires identical type and value. No coercion occurs.
  • Type coercion — automatic conversion of a value from one type to another, e.g. from string "1" to number 1. Also called implicit conversion.
  • Abstract Equality Comparison — the formal algorithm in the ECMAScript specification that defines the behaviour of ==.
  • SameValueZero — an internal comparison algorithm used by Map, Set, and Array.prototype.includes. Treats NaN as equal to NaN, unlike ===.
  • Truthy / falsy — how JavaScript evaluates a non-boolean value in a boolean context. Eight values are falsy; everything else is truthy.
  • Nullish — a value that is null or undefined. The ?? (nullish coalescing) operator and ?. (optional chaining) operator both test for nullish values.
  • eqeqeq — an ESLint rule that requires === instead of ==. One of the most commonly enabled rules in JavaScript linting configurations.
  • NaN (Not a Number) — a special numeric value representing an undefined or unrepresentable result. Uniquely, NaN !== NaN in JavaScript.

Frequently asked questions

Should I always use === in JavaScript?

In almost all cases, yes. Strict equality (===) checks both type and value without any implicit coercion, making comparisons predictable and easy to reason about. ESLint enforces this via the eqeqeq rule. The one widely accepted exception is the null/undefined check: writing if (value == null) catches both null and undefined in a single concise expression — many experienced developers use this deliberately.

What is the abstract equality algorithm?

The abstract equality algorithm (formally the Abstract Equality Comparison in the ECMAScript specification) is the set of rules JavaScript follows when you use ==. It first checks whether the two operands share the same type — if they do, it falls back to strict comparison. If they differ, it attempts type coercion: numbers, strings, booleans, null, undefined, and objects each follow different conversion paths. This produces surprising results such as 0 == "" (true), [] == false (true), and null == undefined (true).

Does == ever make sense in practice?

Yes, in one widely accepted case: null checking. Writing if (value == null) is equivalent to if (value === null || value === undefined), and catches both nullish values in a readable, compact form. Many style guides allow this specific idiom even when banning == everywhere else. For all other comparisons, === is preferable.