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'seqeqeqrule enforces this on most professional teams. - One accepted exception:
value == nullchecks for bothnullandundefinedsimultaneously — a common, deliberate idiom. - NaN quirk:
NaN === NaNisfalse. UseNumber.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()ortoString()to obtain a primitive, then compare. null == undefinedistrue— the spec defines this explicitly.null == anything_elseis alwaysfalse.
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
trueor both must befalse. - 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 === nullandundefined === undefinedare bothtrue.
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== nullis disallowed; you must write the full=== null || === undefinedcheck."smart"— permits== nulland== typeofcomparisons, 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
eqeqeqrule — switch to===." - "
== nullis 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:
[] == falseis 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
nullorundefined→value <strong>==</strong> nullis idiomatic and widely accepted - Comparing a string input to a number → convert explicitly (
Number(str),parseInt(str, 10)), then use=== - Checking for
NaN→ useNumber.isNaN(value), not any equality operator - ESLint is flagging your
==→ switch to===or add aneslint-disable-next-linecomment 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, orArray.prototype.includes→ these use SameValueZero, not===;NaNmembership 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 number1. 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, andArray.prototype.includes. TreatsNaNas equal toNaN, 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
nullorundefined. 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 !== NaNin 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.
Why does [] == false evaluate to true?
This is the abstract equality algorithm at work. JavaScript coerces both operands to numbers before comparing: [] converts to "" (empty string), which converts to 0; false converts to 0; so 0 == 0 is true. Under strict equality, [] === false is false because the types differ (object vs boolean). This specific example is frequently cited in code reviews as a reason to avoid ==.
What is NaN === NaN and why?
NaN === NaN evaluates to false. This is specified by the IEEE 754 standard for floating-point arithmetic. NaN is the only JavaScript value that is not equal to itself. Use Number.isNaN(value) to test for NaN reliably. The abstract equality operator behaves the same: NaN == NaN is also false.
What is the ESLint eqeqeq rule?
eqeqeq is a built-in ESLint rule that requires the use of === and !== instead of == and !=. It can be configured with "always" (ban all uses of ==), "smart" (allow == null and == typeof), or "allow-null" (allow only == null). Most teams enable "always" or "smart". If your team's ESLint config reports an error for ==, this is the rule responsible.
Does TypeScript protect against == coercion bugs?
Partially. TypeScript's type checker will flag comparisons between obviously incompatible types (e.g., comparing a string and a boolean literal). However, comparing two values of type string | number | null still compiles, even with ==, because the types overlap. The safest approach is === combined with TypeScript strict mode — the type system handles type mismatches and === handles the runtime comparison.