ForgeFrontend — Prepare, Practice, Crack
Secure checkout
Lifetime access
Instant PDF download
Free updates forever

Prepare · Practice · Crack

== vs === : Loose vs Strict Equality

Short answer

=== returns true only when both operands have the same type and the same value. == converts the operands to a common type first and then compares, which makes results like 0 == "" true. Use === everywhere, with one deliberate exception: x == null, which catches both null and undefined.

Expression== (loose)=== (strict)
1 == "1"truefalse
0 == ""truefalse
0 == falsetruefalse
null == undefinedtruefalse
null == 0falsefalse
NaN == NaNfalsefalse
[] == ""truefalse
[1] == 1truefalse
{} == {}falsefalse
Same comparisons, two operators.

What each operator does

Strict equality has one rule: if the two operands have different types, the answer is false; otherwise compare the values. That is the whole specification, minus two special cases for NaN and signed zero. It is predictable because it never converts anything.

Loose equality does the same thing when the types already match — for identical types, == and === behave identically. The difference only appears when the types differ, at which point == applies a conversion procedure and compares the results. Every surprising output you have seen comes from that procedure, and it is short enough to actually learn.

Identical types, identical behaviourjavascript
11 == 1;        // → true
21 === 1;       // → true
3"a" == "a";    // → true
4"a" === "a";   // → true
5
6// The operators only diverge across types:
71 == "1";      // → true    "1" is converted to 1
81 === "1";     // → false   number is not string

The coercion rules, in the order they apply

The abstract equality comparison is a short list of ordered rules. Once you know them, no == result is surprising — they are all mechanical consequences.

  1. If the types are the same, compare as === does.
  2. null == undefined is true. Neither is loosely equal to anything else.
  3. Number compared with string: convert the string to a number.
  4. Boolean compared with anything: convert the boolean to a number first — false becomes 0, true becomes 1.
  5. Object compared with a primitive: convert the object to a primitive with valueOf, then toString.
  6. Anything else is false.
Each rule, demonstratedjavascript
1// Rule 2 — the null/undefined pair, and nothing else
2null == undefined;   // → true
3null == 0;           // → false   null is not converted to a number
4undefined == "";     // → false
5
6// Rule 3 — string becomes a number
7"5" == 5;            // → true    Number("5") is 5
8"" == 0;             // → true    Number("") is 0
9" \t\n " == 0;       // → true    whitespace-only strings are 0
10
11// Rule 4 — booleans always become numbers first
12false == 0;          // → true
13false == "";         // → true    false → 0, "" → 0
14true == "1";         // → true    true → 1, "1" → 1
15true == "true";      // → false   Number("true") is NaN

Rule 4 is where most people are caught. A boolean is never compared as a truthiness test — it is converted to a number, and then the other operand is converted to a number too. That is why true == "true" is false while true == "1" is true, which reads like nonsense until you see the two conversions.

The famous surprises, explained

The quiz-question results are all the same three rules applied twice. Being able to derive them, rather than remembering them, is what makes the answer convincing.

Working through the classicsjavascript
10 == "";
2// string vs number → Number("") is 0 → 0 == 0
3// → true
4
5"" == "0";
6// same type, no conversion, "" is not "0"
7// → false     (so == is not transitive)
8
9[] == 0;
10// object vs number → [].toString() is "" → Number("") is 0
11// → true
12
13[] == ![];
14// ![] is false (every object is truthy) → [] == false
15// → boolean becomes 0 → [] becomes "" becomes 0 → 0 == 0
16// → true
17
18[1,2] == "1,2";
19// object vs string → Array.prototype.toString joins with commas
20// → true
21
22null >= 0;
23// → true    relational operators convert null to 0…
24null == 0;
25// → false   …but == has a special rule that does not
26

This is 1 of 80+ questions in the JavaScript Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the JavaScript Interview Kit → ₹299

NaN, signed zero, and Object.is

Strict equality has two behaviours that are not about types at all. NaN is not equal to itself, which is required by the IEEE 754 floating-point standard and is why you cannot test for NaN by comparison. And +0 and -0 compare as equal even though they are distinguishable values.

The two exceptions, and the operator that fixes themjavascript
1NaN === NaN;            // → false
2[NaN].includes(NaN);    // → true    includes uses SameValueZero
3[NaN].indexOf(NaN);     // → -1      indexOf uses ===
4
5Number.isNaN(NaN);      // → true    the correct test
6isNaN("hello");         // → true    the OLD global coerces first — avoid
7
8+0 === -0;              // → true
9Object.is(+0, -0);      // → false
10Object.is(NaN, NaN);    // → true
11
121 / +0;                 // → Infinity
131 / -0;                 // → -Infinity   the difference is observable

Object.is implements what the specification calls SameValue: identical to === except that it treats NaN as equal to itself and distinguishes the two zeros. It is not a deep comparison, and it is not a general replacement for === — reach for it when you specifically need those two behaviours. React uses it internally for dependency and prop comparisons, which is worth knowing when a memo unexpectedly does or does not bail out.

The one place == is the right choice

x == null is true for exactly two values, null and undefined, and false for everything else — including 0, empty string and false. It is the shortest way to express "this has no value", it is explicitly allowed by the default ESLint eqeqeq rule with the smart option, and it appears throughout well-written library code.

The idiom, and its modern alternativesjavascript
1if (value == null) return "unknown";
2// → catches null and undefined only.
3//   Equivalent to: value === null || value === undefined
4
5// It composes with the nullish operators, which use the same test:
6const name = user.name ?? "Anonymous";
7// → falls back only for null/undefined, not for "" or 0
8const city = user.address?.city;
9// → undefined if address is null or undefined, no throw
10
11// Compare with the truthiness check, which is a different question:
12if (!value) return "unknown";
13// → also catches 0, "", NaN and false — usually not what you meant

Comparing objects: neither operator does what you want

Both operators compare objects by reference. Two objects with identical contents are never equal, because they are two different objects in memory. This trips people up because it looks like an equality problem when it is really the reference model at work.

Reference identity, and how to compare contentsjavascript
1{ a: 1 } === { a: 1 };     // → false
2[1,2] === [1,2];           // → false
3
4const x = { a: 1 };
5const y = x;
6x === y;                   // → true    same reference
7
8// Comparing contents needs an explicit strategy:
9JSON.stringify(a) === JSON.stringify(b);
10// → works for plain, JSON-safe data, but key ORDER matters
11//   and it breaks on Dates, Maps, undefined and cycles
12
13// A shallow comparison, which is what React.memo does:
14const shallowEqual = (a, b) =>
15  Object.keys(a).length === Object.keys(b).length &&
16  Object.keys(a).every(k => Object.is(a[k], b[k]));
17// → true for { a: 1 } and { a: 1 }, false if any value differs

This is the same rule that makes React skip a render when you mutate state instead of replacing it, and the same rule that makes an inline object prop defeat memoisation. Equality of objects in JavaScript is identity, not contents, everywhere — the comparison operators are just where you first notice it.

Where else strict equality is used for you

Several language constructs compare with === or something close to it, which matters if you are in the habit of relying on coercion. A switch statement matches cases strictly, so a numeric string will not match a numeric case. Array methods split between two rules: indexOf uses ===, while includes uses SameValueZero, which is why only one of them finds NaN.

Strict comparison in places you did not write itjavascript
1const status = "1";
2switch (status) {
3  case 1:  console.log("number"); break;
4  case "1": console.log("string"); break;
5}
6// → "string"    switch never coerces
7
8[1, 2, 3].indexOf("2");     // → -1     === under the hood
9[1, 2, 3].includes("2");    // → false  SameValueZero, still strict
10
11new Set([1, "1"]).size;     // → 2      Sets use SameValueZero
12new Map([[1, "a"]]).get("1"); // → undefined   keys are strict too

The practical consequence is that a codebase which relies on loose comparison behaves inconsistently: a value that matches in an if statement fails to match the equivalent switch case, and an id that a lookup finds by == is missed by a Map keyed on the same value. Those bugs are hard to spot in review because both lines look correct in isolation. Normalising the type once — at the point where the value enters your code, usually a parsed query string or a form field — removes the whole category, and it is a better habit than remembering which construct coerces.

That is the argument for strict equality in one sentence: it is not that == gives wrong answers, but that it gives answers you have to derive rather than read. Code is read far more often than it is written, and a comparison whose result depends on three conversion rules is a comment waiting to be needed.

Is === faster than ==?

Marginally, because it can skip the conversion step, but the difference is far too small to matter in application code. Prefer it for predictability. If a comparison is genuinely on a hot path, the type check is not what is costing you.

What does [] == ![] return, and why?

true. ![] is false because every object is truthy, so this becomes [] == false. The boolean converts to 0, the array converts to "" and then to 0, and 0 == 0 is true. It is a chain of three rules, which is why it makes a good exam of whether you know them.

Why is NaN not equal to itself?

Because IEEE 754 defines it that way — NaN represents an indeterminate result, and two indeterminate results are not known to be the same. Use Number.isNaN to test for it, or Object.is, which treats NaN as equal to itself.

Does TypeScript stop me using ==?

No. TypeScript will flag a comparison between types that can never overlap, but a == that merely coerces compiles without complaint. Loose equality is a lint concern, not a type-checking one, so enable the eqeqeq rule if you want it enforced.

What is the difference between == null and !value?

They answer different questions. value == null is true only for null and undefined. !value is a truthiness test, so it is also true for 0, "", NaN and false. Using the truthiness check where you meant the null check is a common source of bugs with numeric and text inputs.

Frequently asked questions

What is the difference between == and === in JavaScript?
=== is strict equality: if the operands have different types the result is false, with no conversion. == is loose equality: when the types differ it converts them to a common type and then compares, which is why 1 == "1" is true and 1 === "1" is false.
Should I always use === in JavaScript?
Almost always. The one accepted exception is x == null, which tests for null and undefined together and is explicitly permitted by the standard ESLint rule. Everywhere else, strict equality removes a class of bugs at no cost.
Why does 0 == "" return true?
When a number is compared with a string, the string is converted to a number. Number("") is 0, so the comparison becomes 0 == 0. The same rule makes " " == 0 true, because a whitespace-only string also converts to 0.
Why does [] == ![] return true?
![] is false, since all objects are truthy. That gives [] == false. Booleans convert to numbers, so false becomes 0. The array converts to a primitive — an empty string — which then converts to 0. Both sides are 0, so the result is true.
Is null equal to undefined?
With ==, yes — the specification has an explicit rule making them loosely equal to each other and to nothing else. With ===, no, because they are different types. That special rule is exactly what makes the x == null idiom useful.
Why is NaN not equal to NaN?
IEEE 754 defines NaN as unequal to every value including itself, because it stands for an indeterminate result. Test for it with Number.isNaN, or use Object.is(x, NaN), which treats NaN as equal to itself.
What is the difference between === and Object.is?
Object.is matches === except in two cases: it returns true for Object.is(NaN, NaN) and false for Object.is(+0, -0). It is not a deep comparison. React uses this SameValue semantics internally when comparing props and hook dependencies.
Does === compare objects by value?
No. Both operators compare objects by reference, so two objects with identical contents are never equal. Comparing contents requires an explicit strategy — a shallow comparison of keys, or a deep-equality helper for nested data.
Is === faster than ==?
Slightly, because there is no conversion step, but the difference is negligible in real code. Choose === for predictability and readability rather than for speed.
Does the switch statement use == or ===?
===. Cases are matched strictly, so case 1 will not match the string "1". This surprises people who are used to loose comparison elsewhere, and it is a common source of a switch that silently falls through to the default.
Why does indexOf not find NaN but includes does?
indexOf compares with ===, and NaN === NaN is false. includes uses SameValueZero, which treats NaN as equal to itself. That is the only difference in comparison behaviour between the two methods.
What does the eqeqeq ESLint rule do?
It reports every use of == and !=. With the "smart" option it allows comparison against null, along with a couple of other narrow cases, which encodes the usual convention: strict equality everywhere except the deliberate null check.

This is 1 of 80+ questions in the JavaScript Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the JavaScript Interview Kit → ₹299
Written by Arun Karthikeyan · Last updated

Full kit

JavaScript Interview Kit · ₹299

Get it →