Quick Answer

== is loose equality: it converts the two values to the same type before comparing, which leads to surprising results like 0 == '' being true. === is strict equality: it returns true only when the values have the same type and the same value, with no conversion. For almost all everyday code you should use ===, because it is predictable and prevents type-coercion bugs.

Two equals operators, one big difference

JavaScript gives you two ways to check whether two values are equal: == (two equals signs) and === (three equals signs). They look almost the same, and beginners often use them interchangeably — but they behave differently, and mixing them up is a classic source of bugs. If you have ever searched for double equals vs triple equals javascript, this guide clears it up for good.

Here is the one-line summary before we dig in:

  • === is strict equality. It compares value and type, and never converts anything.
  • == is loose equality. It tries to convert the two values to the same type first, then compares them.

That single difference — whether the values get converted before comparison — explains every surprising result you will see below.

=== strict equality: same type, same value

=== asks a simple question: are these two values the same type and the same value? If the types differ, the answer is immediately false — no conversion, no guessing.

5 === 5             // true  — same type (number), same value
5 === '5'           // false — number vs string, different types
true === 1          // false — boolean vs number
null === undefined  // false — different types
'hi' === 'hi'       // true

Because it has no hidden conversions, === is predictable: what you see is what you compare. This is why it is often called the "strict" or "identity" operator, and why it is the default choice in professional codebases.

== loose equality: convert first, then compare

== is more forgiving — sometimes too forgiving. When the two values are different types, it runs a set of conversion rules (the language spec calls this "type coercion") to make them match, and only then compares them.

5 == 5      // true
5 == '5'    // true  — the string '5' is converted to the number 5
true == 1   // true  — true is converted to 1
false == 0  // true  — false is converted to 0
'' == 0     // true  — the empty string becomes 0

A rough version of the rules == follows:

  • If the two types are already the same, it behaves exactly like ===.
  • Comparing a number and a string? The string is converted to a number.
  • Comparing a boolean with anything? The boolean becomes a number first (true becomes 1, false becomes 0).
  • Comparing an object with a primitive? The object is converted to a primitive value first.
  • null and undefined are loosely equal to each other, and to nothing else.

Those rules are logical once you know them, but you have to remember them for every comparison — which is exactly the mental overhead that === saves you.

The surprising cases that trip people up

Type coercion produces some results that look plain wrong at first. These are the examples interviewers love and the bugs that cost hours of debugging:

0 == ''            // true  — '' becomes 0
0 == '0'           // true  — '0' becomes 0
'' == '0'          // false — both are strings, no conversion; '' is not '0'
null == undefined  // true  — special rule
null == 0          // false — null only loosely equals undefined
undefined == 0     // false — same reason
NaN == NaN         // false — NaN is never equal to anything, even itself
[] == false        // true  — [] becomes '', which becomes 0; false becomes 0

Look closely at the first three lines. 0 == '' is true and 0 == '0' is true, yet '' == '0' is false. That means == is not even consistent in the way normal equality should be: if A equals B and B equals C, you would expect A to equal C, but coercion breaks that.

The null == undefined case is the one genuinely useful piece of loose equality, and we will come back to it. Everything else on this list is a reason to be careful.

Double equals vs triple equals in JavaScript: side-by-side

Here is the whole comparison in one place:

Behaviour== (loose)=== (strict)
Converts types before comparingYesNo
Requires the same type to be equalNoYes
Predictable and easy to reason aboutPartialYes
Treats 5 and '5' as equalYesNo
Recommended for everyday codeNoYes

The pattern is clear: === is strict and predictable in every row, while == takes shortcuts that can surprise you.

A quick note on truthy and falsy

Equality is about comparing two values. Truthiness is a related but separate idea: what happens when a single value is used where JavaScript expects a boolean, such as inside an if statement.

JavaScript has a small, fixed set of falsy values — values that act like false in a condition: false, 0 (and -0), 0n (the BigInt zero), '' (empty string), null, undefined, and NaN. Every other value is truthy.

The tricky part for beginners: some values that look empty or false are actually truthy. The string '0', the string 'false', an empty array [], and an empty object {} are all truthy.

if ('0') {
  console.log('runs!');        // '0' is a non-empty string, so it is truthy
}

if ([]) {
  console.log('this runs too'); // an empty array is truthy
}

Here is the trap: [] == false is true (because of coercion), yet [] is truthy inside an if. Loose equality and truthiness use different rules, so never assume one predicts the other. Sticking to === for comparisons and clear conditions for truthiness keeps the two ideas from colliding.

The simple rule: almost always use ===

Use === (and its opposite !==) by default. Reach for == only in the one narrow case below.

Why make === the default?

  • It is predictable. No hidden conversions means fewer surprises and fewer bugs.
  • It shows intent. Anyone reading your code knows you meant an exact match.
  • Tools expect it. Popular linters like ESLint warn you when you use ==, precisely because of the coercion traps above.

The one place loose equality genuinely helps is checking for "no value" — both null and undefined at once:

if (value == null) {
  // true when value is null OR undefined
  // a clean, widely used shortcut
}

Because null == undefined is true and null / undefined equal nothing else, value == null is a well-known, safe idiom. Outside of this specific check, stick with ===. Want to practise this on real exercises? Our free JavaScript course has runnable examples for equality, type coercion, and conditions.

Common gotchas to remember

  • Objects and arrays compare by reference, not contents. Even with ===, two separate objects are never equal: {} === {} is false, and [1] === [1] is false. They are equal only when both variables point at the exact same object.
  • NaN is never equal to anything. NaN === NaN is false. To check for NaN, use Number.isNaN(value).
  • == is not "transitive". As we saw, 0 == '' and 0 == '0' are both true, but '' == '0' is false. Do not rely on chains of loose comparisons.
  • Need to treat NaN as equal, or -0 as different from 0? That is what Object.is() is for — an advanced tool you will rarely need as a beginner.

Keep it simple and you will avoid nearly every equality bug in JavaScript: reach for === by default, use value == null for the null-or-undefined check, and remember that objects are compared by reference. Do that, and the confusing parts of double equals vs triple equals stop being confusing.

Frequently Asked Questions

Should I ever use == in JavaScript?

Almost never. The main exception is value == null, which conveniently checks for both null and undefined in one step. For every other comparison, use === so you avoid type-coercion surprises.

Why is 0 == '' true in JavaScript?

Because == converts both values to numbers before comparing. The empty string '' becomes 0, so the comparison is really 0 == 0, which is true. With ===, 0 === '' is false because a number and a string are different types.

Is === faster than ==?

It can be very slightly faster because it skips the type-conversion step, but the difference is far too small to matter in real programs. Choose === for correctness and readability, not for speed.

How do I check if a value is null or undefined?

Use value == null. Thanks to a special rule, null == undefined is true and neither equals anything else, so this single check catches both. If you prefer to be explicit, you can write value === null || value === undefined.

Why does NaN === NaN return false?

NaN ("Not a Number") is defined to be unequal to everything, including itself, so both NaN == NaN and NaN === NaN are false. To test whether a value is NaN, use Number.isNaN(value).

Does === compare objects by their contents?

No. For objects and arrays, === checks whether both variables point to the exact same object in memory, not whether their contents match. That is why {} === {} and [1] === [1] are both false. To compare contents, you compare the individual properties yourself.