Loose (==) vs strict (===) equality in JavaScript
Strict equality (===) checks value and type without any conversion. Loose equality (==) converts operands to a matching type first, then compares the results.
Theory
TL;DR
===is like matching exact shoe size and color;==stretches the shoe to fit any foot===skips all type conversion;==runs the Abstract Equality Comparison algorithm5 === "5"is false;5 == "5"is true (string converts to number)null == undefinedis true by spec;null === undefinedis false- Default rule: use
===everywhere;==is acceptable only fornull/undefinedchecks
Quick example
console.log(5 === 5); // true
console.log(5 === "5"); // false - number vs string, no conversion
console.log(5 == "5"); // true - "5" converts to 5
console.log(true === 1); // false - boolean vs number
console.log(true == 1); // true - true converts to 1
console.log(null == undefined); // true - special rule in the spec
console.log(null === undefined);// false - different types=== stops at the type check. == keeps going and converts.
Key difference
=== uses the SameValue algorithm: compare type tags first, then values. No conversion, no surprises. == triggers the Abstract Equality Comparison, which runs ToNumber on strings, converts booleans to numbers (true to 1, false to 0), and handles objects via valueOf/toString. That chain is why [] == 0 is true: [] calls toString() to get "", then "" converts to 0 via ToNumber.
When to use
- Any normal comparison: use
=== - Checking for
nullorundefinedtogether:value == nullcatches both in one line - Comparing user input to a number after
parseInt: use===explicitly - Legacy code where coercion is intentional:
==with a comment explaining why
Comparison table
| Aspect | === (Strict) | == (Loose) |
|---|---|---|
| Type check | Yes, type + value | No, converts first |
| Coercion | None | Automatic (string to number, etc.) |
| Predictability | High | Low |
| Performance | Slightly faster | Slightly slower |
5 == "5" | false | true |
null == undefined | false | true |
| When to use | All comparisons | Only value == null |
How it works internally
V8 implements === as a direct type-tag comparison followed by a value check. No allocations, no conversions. For ==, the engine runs ToPrimitive on objects (calling valueOf then toString), then ToNumber on strings. The coercion chain can go three steps deep before settling on a result, which is why edge cases like [] == 0 feel counterintuitive.
Common mistakes
Using == in a conditional expecting a single type:
// Wrong - "0" and [] also match
if (userInput == 0) { ... }
// Fix
if (userInput === 0) { ... }Checking an empty array with ==:
// Wrong - never true, object references differ
if (data == []) processData();
// Fix
if (data.length === 0) processData();Forgetting that NaN is never equal to itself:
console.log(NaN === NaN); // false
console.log(NaN == NaN); // false - same rule applies
// Fix
Number.isNaN(value); // correct checkLoop condition with string input:
// Wrong - string "1" matches, loop runs unexpectedly
while (count == 1) count--;
// Fix
while (count === 1) count--;Real-world usage
- React:
props.id === expectedIdin component guards - Express:
req.params.id === 'new'in route handlers - Node.js:
process.env.NODE_ENV === 'production' - Redux:
action.type === types.FETCH_SUCCESS - ESLint rule
eqeqeqenforces===across a codebase automatically
Follow-up questions
Q: Why does [] == 0 return true?
A: [] calls toString() to get "", then "" converts to 0 via ToNumber. So [] == "" and "" == 0, making [] == 0 true.
Q: Is null == undefined true? Why?
A: Yes, it is a special case defined in the Abstract Equality Comparison spec. Only null and undefined equal each other via ==; neither equals 0, false, or "".
Q: NaN === NaN is false. How do you check for NaN?
A: Use Number.isNaN(value). The x !== x trick also works, since NaN is the only value that fails equality against itself. Object.is(value, NaN) is another option.
Q: Does == ever belong in production code?
A: One case: if (value == null) catches both null and undefined in one comparison. Some teams use it deliberately for that pattern. Outside that, === is the safe default.
Q: What does Object.is() add over ===?
A: Two edge cases: Object.is(NaN, NaN) is true (unlike ===), and Object.is(+0, -0) is false (unlike ===). For most code === is fine, but Object.is matches the SameValue spec exactly.
Examples
Basic coercion behavior
// Strict equality - type must match
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(false === 0); // false
// Loose equality - coercion in action
console.log(5 == "5"); // true - string to number
console.log(false == 0); // true - false to 0
console.log("" == 0); // true - empty string to 0
console.log("0" == false); // true - both become 0== skips conversion only when both sides already share the same type.
React component pattern
function UserCard({ userId, isActive }) {
// Strict check - prevents "0" from being treated as a missing prop
if (userId === undefined) return <div>No user</div>;
// Safe null check with == - catches both null and undefined
if (isActive == null) return <div>Status unknown</div>;
return <div>User {userId} is {isActive ? "active" : "inactive"}</div>;
}
// process.env pattern - always strict
const isProd = process.env.NODE_ENV === "production";I started defaulting to === everywhere after a loop ran one extra iteration because an API returned the string "1" instead of the number 1. The type mismatch was invisible until I added a typeof check. One character (== to ===) would have caught it immediately.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.