JavaScript == vs ===: Truthy, Falsy and Type Coercion
JavaScript has eight falsy values, and == converts types by fixed rules while === never does. How coercion works, why 1 + "2" is "12", and || vs ??.
- Course: JavaScript study plan
- Module: Values, types and coercion
- Kind: Lesson
- Reading time: 14 min
- Runtime: Node 16
What is the difference between == and === in JavaScript?
=== (strict equality) compares type and value with no conversion, while == (loose equality) first converts the operands by fixed rules: null and undefined equal each other and nothing else, a string compared with a number becomes a number, a boolean becomes a number, and an object becomes a primitive. So 1 == "1" is true but 1 === "1" is false. Use ===; x == null is the one idiomatic ==.
Lesson
JavaScript will happily compare a string to a number, add a number to a string, and treat an empty array as true and an empty string as false. The rules behind those conversions are precise, small in number, and mostly ignored — which is why == has a reputation and "1" + 1 is a meme. This lesson states the rules: what is falsy, how == decides, what + does, what the comparison operators do to mixed types, and the modern operators (??, ?., ||=) that let you write the common cases without thinking about any of it.
Truthy and falsy
Exactly eight values are falsy: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy — including "0", "false", [], {}, and any function. Conditions (if, while, ? :, !, &&, ||) apply this rule, and Boolean(x) or !!x makes it explicit.
if ([]) console.log("an empty array is truthy"); // prints
if ("") console.log("never");
if ("0") console.log("a non-empty string is truthy"); // prints
The trap: if (count) is wrong when 0 is a valid count; write if (count !== undefined) or if (count != null).
&&, ||, ?? return operands, not booleans
a && b returns a if a is falsy, else b. a || b returns a if truthy, else b. Both short-circuit — the right side is not evaluated when the left decides. This is why they double as control flow:
const name = input || "anonymous"; // default — but "" and 0 are replaced too
const name2 = input ?? "anonymous"; // nullish coalescing (2020): only null/undefined are replaced
user && user.save(); // call only if user is truthy
user?.save(); // optional call (2020): only if user is not null/undefined
config.retries ??= 3; // logical assignment (2021): set if nullish
flags.verbose ||= false; cache.hit &&= true;
Use ?? for defaults whenever 0, "" or false are legitimate values — which is most of the time. ?. works for properties (a?.b), computed access (a?.[k]) and calls (f?.()), and short-circuits the rest of the chain: a?.b.c is undefined when a is nullish, without evaluating .c.
=== and ==
Strict equality ===: same type and same value; no conversion. Objects by identity. NaN !== NaN; +0 === -0. Object.is is === except that it distinguishes -0 and treats NaN as equal to itself.
Loose equality == converts operands by these rules, in order:
- Same type → same as
===. null == undefined→true(and neither equals anything else).- number vs string → convert the string to a number:
1 == "1". - boolean vs anything → convert the boolean to a number:
true == 1,true == "1",false == "",false == "0". - object vs primitive → convert the object with
valueOf/toString:[1] == 1,[] == "",[] == 0,{} == "[object Object]".
Hence the famous table: "" == 0 (true), "0" == 0 (true), "" == "0" (false — same type, different value), null == 0 (false — rule 2 stops null), [] == ![] (true — ![] is false, false → 0, [] → "" → 0). None of this is random; all of it is avoidable: use ===, and the single idiomatic ==: x == null to test for null-or-undefined.
Relational operators
< > <= >= convert to numbers unless both sides are strings, in which case they compare code units:
"10" < "9" // true — string comparison
10 < "9" // false — "9" becomes 9
"abc" < 5 // false — "abc" becomes NaN, and every comparison with NaN is false
null < 1 // true — null becomes 0
undefined < 1 // false — undefined becomes NaN
Sorting an array of numeric strings with < or the default sort() gives ["1", "10", "2", "9"]; convert or pass a comparator.
+ is the odd one out
If either operand is a string, + concatenates; otherwise it adds numbers. Every other arithmetic operator (- * / %) converts to numbers.
1 + "2" // "12"
"3" - 1 // 2
"3" * "4" // 12
1 + 2 + "3" // "33" — left to right: 3 + "3"
"1" + 2 + 3 // "123"
[] + {} // "[object Object]" — both become strings
+"42" // 42 — unary plus converts
The conversion algorithm, briefly
Converting an object to a primitive calls Symbol.toPrimitive if present, else valueOf then toString (for numeric contexts) or toString then valueOf (for string contexts). Date prefers strings in +; arrays' toString is join(","); plain objects give "[object Object]". You can define toString/valueOf on your own classes to control this — rarely wise, occasionally elegant (a Money class that formats itself).
Conditions that read well
if (list.length === 0) // not: if (!list.length) — explicit intent
if (value == null) // the one accepted ==
if (typeof x === "string" && x !== "")
const port = Number(process.env.PORT ?? 3000);
const label = user?.profile?.displayName ?? user?.email ?? "guest";
Reviewers read === as competence and == as a bug unless it is the null check. Reach for ?? over || unless you specifically want to replace empty strings and zeros.
Interview angle
- "What is falsy in JavaScript?"
false, 0, -0, 0n, "", null, undefined, NaN— and nothing else;[]and{}are truthy. - "
==versus===?" Loose equality converts by fixed rules (null/undefined equal; strings to numbers; booleans to numbers; objects to primitives); strict compares type and value. Use===;== nullis the one idiom. - "
||versus???"||replaces any falsy left side;??onlynull/undefined. - "Why is
1 + \"2\"\"12\"but\"3\" - 1is2?"+concatenates if either side is a string; the other operators convert to numbers. - "Is
[] == ![]true?" Yes — and the derivation is the interviewer's point; the answer is to never write it.
Key takeaways
- Eight falsy values; everything else truthy — including
[],{}and"0". &&/||/??return operands and short-circuit;??and?.handle only nullish values.===always;== nullis the one idiomatic loose equality; know the coercion rules to explain, not to use.+concatenates when a string is involved;- * / %convert to numbers; strings compare by code unit.Object.isforNaNand-0;Number.isNaNforNaN.
Common questions
What are the falsy values in JavaScript?
Exactly eight values are falsy: false, 0, -0, 0n, "", null, undefined and NaN. Everything else is truthy, including "0", "false", an empty array [] and an empty object {}.
What is the difference between || and ?? in JavaScript?
a || b returns b whenever a is falsy, so it also replaces 0, "" and false. a ?? b, nullish coalescing, returns b only when a is null or undefined, which makes it the right choice for defaults where zero or an empty string is a valid value.
Why is 1 + "2" equal to "12" in JavaScript?
If either operand of + is a string, + concatenates, so 1 + "2" is "12". Every other arithmetic operator converts its operands to numbers, which is why "3" - 1 is 2 and "3" * "4" is 12.
Why is [] == ![] true in JavaScript?
![] is false, because an empty array is truthy. == then converts false to 0 and the array to the primitive "", which also becomes 0, so both sides are 0. The practical lesson is to use === and never write it.
What does optional chaining do in JavaScript?
a?.b gives undefined instead of throwing when a is null or undefined, and short-circuits the rest of the chain, so a?.b.c never evaluates .c. It also works for computed access (a?.[k]) and calls (f?.()).
Exercises
Falsy filter
Read n tokens and convert each with the parse helper (empty is the empty string, 0n a bigint zero, [] an empty array…). Print <token>: truthy or <token>: falsy using Boolean(value), then falsy=<count>. Exactly eight values are falsy — check your list against the output.
Example: 5 then 0 empty [] NaN hello →
0: falsy
empty: falsy
[]: truthy
NaN: falsy
hello: truthy
falsy=3Loose, strict, or the same?
Read n pairs of tokens, convert each with parse, and print <a> <b>: ==<a == b> ===<a === b> is=<Object.is(a, b)>. Every difference between the three columns is one of the rules from the lesson.
Example: 3 then null undefined, 0 empty, NaN NaN →
null undefined: ==true ===false is=false
0 empty: ==true ===false is=false
NaN NaN: ==false ===false is=trueIn this module: Values, types and coercion
- The seven primitives, objects and typeof
- Numbers — doubles, NaN, safe integers and BigInt
- Strings — immutable text, template literals and the method toolkit
- Truthiness, coercion and the two equalities (this lesson)
- let, const and var — scope, hoisting and the temporal dead zone
- Checkpoint — Values, types and coercion
← Strings — immutable text, template literals and the method toolkit · let, const and var — scope, hoisting and the temporal dead zone →