=== (strict equality) compares type and value with no conversion; == (loose equality) first tries to convert the operands to a common type, following a set of coercion rules that are notoriously easy to get wrong:
0 == "0"; // true — "0" is coerced to the number 0
0 == ""; // true — "" is coerced to 0 as well
0 == false; // true — false is coerced to 0
"" == false; // true — both coerced to 0
null == undefined; // true — a special case, but null === undefined is false
1 == "1"; // true — "1" coerced to 1
[] == false; // true — [] is coerced to "" then to 0
The practical rule almost every style guide agrees on: use === by default, always. Reach for == only in the one case it's actually idiomatic — x == null, which conveniently catches both null and undefined in a single check without needing x === null || x === undefined.
Neither == nor === compares object/array contents — they compare references for anything that isn't a primitive:
[1, 2] === [1, 2]; // false — two different array objects, even with identical contents
{ a: 1 } === { a: 1 }; // false — same reason
const arr = [1, 2];
arr === arr; // true — same exact reference
Comparing contents requires either a manual field-by-field check, JSON.stringify(a) === JSON.stringify(b) (fragile — sensitive to key order and doesn't handle functions/dates well), or a library helper (isEqual from Lodash, or a framework's own deep-equality utility).