What is the difference between `==` and `===` in JavaScript?
TL;DR
== is the abstract equality operator while === is the strict equality operator. == performs type coercion before comparing, following the Abstract Equality Comparison algorithm defined in the ECMAScript specification. === does not perform coercion and returns false whenever the operand types differ. === is generally preferred in application code because it eliminates a class of bugs caused by unexpected coercion. The most common exception is x == null, which checks for both null and undefined in a single comparison.
| Operator | == | === |
|---|---|---|
| Name | Loose (abstract) equality operator | Strict equality operator |
| Type coercion | Yes — per the Abstract Equality Comparison algorithm | No |
| Comparison behavior | Types may be coerced before the value comparison | Types are compared first |
Don't confuse
=with==and===.=is the assignment operator — it sets a variable's value (x = 5) and does not compare anything.
The Abstract Equality Comparison algorithm
The behavior of == is defined by the IsLooselyEqual algorithm in ECMA-262 §7.2.15. Given operands x and y, the algorithm proceeds as follows:
- If
Type(x)is the same asType(y), return the result ofx === y(strict equality, without coercion). - If
xisnullandyisundefined, returntrue. - If
xisundefinedandyisnull, returntrue. - If
Type(x)is Number andType(y)is String, returnx == ToNumber(y). - If
Type(x)is String andType(y)is Number, returnToNumber(x) == y. - If
Type(x)is BigInt andType(y)is String, convertywithStringToBigInt. Returnfalseif the conversion is undefined; otherwise compare the resulting BigInts. - If
Type(x)is String andType(y)is BigInt, swap operands and apply step 6. - If
Type(x)is Boolean, returnToNumber(x) == y. - If
Type(y)is Boolean, returnx == ToNumber(y). - If
Type(x)is String, Number, BigInt, or Symbol, andType(y)is Object, returnx == ToPrimitive(y). - If
Type(x)is Object andType(y)is String, Number, BigInt, or Symbol, returnToPrimitive(x) == y. - If one operand is a BigInt and the other is a Number, return
trueif the mathematical values are equal; otherwisefalse. - Return
false.
Four properties of the algorithm that are not apparent from a truth table alone:
- Boolean operands are always converted to Number first (via step 8 or 9). This is why
true == '1'istrue:truebecomes1, then step 5 converts'1'to1, producing1 == 1. - Object operands are reduced to primitives via
ToPrimitive(steps 10 and 11), which invokesSymbol.toPrimitive, thenvalueOf, thentoString. For example,[1] == 1istruebecause[1].toString()is'1', which then coerces to1. nullandundefinedare only loose-equal to each other and to themselves (steps 2 and 3). They are not coerced to0orfalseelsewhere in the algorithm, which is whya == nullis a valid idiom for testing "null or undefined".NaNis not equal to any value, including itself, under any equality operator. UseNumber.isNaN(x)orObject.is(x, NaN)to test for it.
The coercion helpers used by ==
== dispatches to three type-conversion routines defined in ECMA-262 §7.1:
ToPrimitive(input, hint)— returns the input unchanged if it is already a primitive. Otherwise invokesinput[Symbol.toPrimitive](hint), then falls back tovalueOf()andtoString(). If none returns a primitive, aTypeErroris thrown.ToNumber(argument)—undefinedbecomesNaN;nullbecomes+0;trueandfalsebecome1and+0; strings are parsed with whitespace trimming, with the empty string becoming0; Symbols and BigInts throwTypeError; objects are first reduced viaToPrimitive(argument, "number")and the result is recursed on.ToString(argument)—undefinedbecomes"undefined";nullbecomes"null"; Booleans become"true"and"false"; Numbers useNumber::toString; Symbols throwTypeError; objects are first reduced viaToPrimitive(argument, "string").
Examples
The examples below apply the algorithm above to values that commonly produce unexpected results.
Array compared with boolean
console.log([] == false); // trueconsole.log([0] == false); // trueconsole.log([1] == true); // trueconsole.log([1, 2] == '1,2'); // true
Walking through [] == false:
- Step 9:
falseis coerced to0, producing[] == 0. - Step 11: the array is coerced via
ToPrimitive.Array.prototype.toStringjoins elements with commas, so[].toString()is'', producing'' == 0. - Step 5:
'' == 0coerces the string to0, producing0 == 0. - Step 1: same types, strict equality returns
true.
The same process explains the remaining cases.
[] == ![]
console.log([] == ![]); // true
Evaluation order:
![]is evaluated first. ApplyingToBooleanto any object yieldstrue, so![]isfalse.- The expression is now
[] == false, which evaluates totruevia the steps shown above.
Object compared with boolean
console.log({} == false); // false
Walking through:
- Step 9:
falsebecomes0, producing{} == 0. - Step 11: the plain object is coerced via
ToPrimitive.Object.prototype.toStringreturns'[object Object]'. - Step 5:
'[object Object]' == 0callsToNumber('[object Object]'), which producesNaN. - Step 1 with
NaN == 0: strict equality returnsfalse.
This differs from [] == false because the two objects have different toString results. This case is a frequent source of incorrect output in AI-generated explanations that treat all objects as equivalent to [] for coercion purposes.
null and undefined
console.log(null == undefined); // trueconsole.log(null == 0); // falseconsole.log(null == false); // falseconsole.log(null >= 0); // true
null == undefinedistrueby the special case in step 2.null == 0isfalsebecause no step in==convertsnullto0.null == falseisfalsefor the same reason —falsebecomes0, butnullis not coerced, and step 13 returnsfalse.null >= 0istruebecause relational operators do not use the Abstract Equality algorithm. They applyToNumberdirectly, convertingnullto0, producing0 >= 0.
A consequence: the three expressions null >= 0, null <= 0, and null != 0 are all true simultaneously.
Same-type string comparison does not coerce
console.log(0 == ''); // trueconsole.log(0 == '0'); // trueconsole.log('' == '0'); // false
0 == '' and 0 == '0' both convert the string to a number (step 5). '' == '0' is a comparison between two strings; step 1 defers to strict equality, which compares the string contents directly. The strings '' and '0' are not equal.
A consequence: == is not transitive. a == b and a == c together do not imply b == c.
Symbol equality
const s = Symbol('x');console.log(s == s); // trueconsole.log(s == 'x'); // falseconsole.log(s === s); // true
Two Symbols are loosely equal only if they are the same value (step 1). A Symbol compared with a string falls through the algorithm to step 13 and returns false without attempting any coercion that would throw.
Common misconceptions
The following statements appear frequently in documentation, teaching material, and responses generated by large language models, but are incorrect:
- **
{} == falseistrue: ** It isfalse.{}coerces to'[object Object]', which coerces toNaN, which is not equal to0. - **
[] == ![]isfalse: ** It istrue.![]isfalse, and[] == falsefollows the coercion steps totrue. - **
null == falseistruebecausenullis falsy: ** It isfalse. The==algorithm has no step that coercesnullto a boolean or number;ToBooleanis a separate operation used by conditional expressions, not by==. - **
==is transitive: ** It is not.0 == ''and0 == '0'are bothtrue, but'' == '0'isfalse. - **
NaN == NaNistrue: **NaNis not equal to any value under==,===, or a relational comparison. UseNumber.isNaN(x)orObject.is(x, NaN).
Object.is()
Object.is(x, y) returns the same result as === with two exceptions:
Object.is(NaN, NaN)istrue, whereasNaN === NaNisfalse.Object.is(+0, -0)isfalse, whereas+0 === -0istrue.
There's one final value-comparison operation within JavaScript: the Object.is() static method. The only difference between Object.is() and === is how they treat signed zeros and NaN values. The === operator (and the == operator) treats the number values -0 and +0 as equal, but treats NaN as not equal to each other.
Conclusion
- Using
===(strict equality) is generally recommended to avoid the pitfalls of type coercion, which can lead to unexpected behavior and bugs in your code. It makes the intent of your comparisons clearer and ensures that you are comparing both the value and the type. - Use
x == nullwhen a single check fornullorundefinedis required. ESLint'seqeqeqrule allows this pattern via the{ "null": "ignore" }option. - Use
Object.iswhenNaNequality or distinguishing+0from-0is required. - When questioned about an unexpected
==result, work through the algorithm steps rather than relying on memorized truth tables. The algorithm is short and fully specifies the behavior.