Skip to main content

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 algorithm
  • 5 === "5" is false; 5 == "5" is true (string converts to number)
  • null == undefined is true by spec; null === undefined is false
  • Default rule: use === everywhere; == is acceptable only for null/undefined checks

Quick example

javascript
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 null or undefined together: value == null catches 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 checkYes, type + valueNo, converts first
CoercionNoneAutomatic (string to number, etc.)
PredictabilityHighLow
PerformanceSlightly fasterSlightly slower
5 == "5"falsetrue
null == undefinedfalsetrue
When to useAll comparisonsOnly 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:

javascript
// Wrong - "0" and [] also match if (userInput == 0) { ... } // Fix if (userInput === 0) { ... }

Checking an empty array with ==:

javascript
// Wrong - never true, object references differ if (data == []) processData(); // Fix if (data.length === 0) processData();

Forgetting that NaN is never equal to itself:

javascript
console.log(NaN === NaN); // false console.log(NaN == NaN); // false - same rule applies // Fix Number.isNaN(value); // correct check

Loop condition with string input:

javascript
// Wrong - string "1" matches, loop runs unexpectedly while (count == 1) count--; // Fix while (count === 1) count--;

Real-world usage

  • React: props.id === expectedId in 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 eqeqeq enforces === 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

javascript
// 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

javascript
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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?