JavaScript Gotchas Cheat Sheet
Fifteen JavaScript traps, from == coercion to lost this bindings, each with a one-line fix. Build the mental models to spot them in code you write, read, or review, whether you or an AI wrote it. You can also download the information as a printable cheat sheet:
Free Bonus: JavaScript Gotchas Cheat Sheet
Get the JavaScript Gotchas Cheat Sheet (PDF), a printable reference to loose equality, typeof null, NaN, floating-point math, sort(), this binding, hoisting, reference equality, semicolon insertion, parseInt, for...in on arrays, string concatenation, Date months, shallow freeze, and array holes:
Want a guided path instead of a reference? Start learning at realjs.com.
Equality & Types
==converts types before comparing, so its results are hard to predict- Default to
===and!==
Loose Equality Surprises
0 == "" // true
"0" == false // true
[] == false // true
null == 0 // false
null == undefined // true
Fix: Use Strict Equality
0 === "" // false
"0" === false // false
value == null // OK: null or undefined
typeof null
typeof null // "object" (a bug)
// Fix: test for null directly
value === null // true if null
NaN
NaNis the only value not equal to itself- Global
isNaN()converts first
Check for NaN
NaN === NaN // false
isNaN("abc") // true (misleading)
Number.isNaN("abc") // false
Number.isNaN(NaN) // true
Floating-Point Math
- Numbers are binary floats, so many decimals are approximations
- Store money as integer cents
The Problem
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.2 === 0.3 // false
1.005.toFixed(2) // "1.00"
Fixes
// Pick a tolerance that fits your scale
const near = (a, b) =>
Math.abs(a - b) < 1e-9;
near(0.1 + 0.2, 0.3) // true
// Use integer cents, format at the end
1999 + 501 // 2500
(2500 / 100).toFixed(2) // "25.00"
Sorting & Parsing
sort()compares items as strings by default- Always pass a radix to
parseInt()
sort() Without a Comparator
[10, 9, 1].sort() // [1, 10, 9]
Fix: Compare Numbers
[10, 9, 1].toSorted((a, b) => a - b)
// [1, 9, 10]
parseInt Quirks
parseInt("12px") // 12
parseInt("0.5") // 0
parseInt(0.0000005) // 5
["1", "2", "3"].map(parseInt)
// [1, NaN, NaN]
Fix: Be Explicit
parseInt("ff", 16) // 255
["1", "2", "3"].map(Number)
// [1, 2, 3]
Number("12px") // NaN
Free Bonus: Download the JavaScript Gotchas Cheat Sheet PDF and keep the essentials at hand.
Comparing by Reference
- Objects are equal only if they are the same object
Gotcha and Fix
[] === [] // false
const a = [1];
const b = a;
a === b // true
// Compare contents (key order matters)
JSON.stringify(a) === JSON.stringify(b)
Looping Over Arrays
Keys vs Values
const a = [1, 2];
a.x = "y";
for (const k in a) console.log(k);
// "0", "1", "x" (strings!)
// Fix: for...of gives values
for (const v of a) console.log(v);
// 1, 2
How this Gets Bound
thisdepends on how a function is called, not where it is written- Arrow functions use the
thisaround them - Same results in scripts and ES modules;
this?.avoids a moduleTypeError
Lost Method Reference
const user = {
name: "Ada",
hi() { return this?.name; },
};
user.hi() // "Ada"
const hi = user.hi;
hi() // undefined
Fix: bind() or a Wrapper
user.hi.bind(user)() // "Ada"
const run = () => user.hi();
run() // "Ada"
Arrow vs function in Callbacks
const t = {
n: "T",
a() { return [1].map(() => this.n); },
f() {
return [1].map(function () {
return this?.n;
});
},
};
t.a() // ["T"]
t.f() // [undefined]
Hoisting & TDZ
varis hoisted and starts asundefinedletandconstexist but can’t be touched before their line
Same Name, Different Failures
typeof v // "undefined"
var v = 1;
l // ReferenceError (TDZ)
let l = 2;
Free Bonus: Download the JavaScript Gotchas Cheat Sheet PDF and keep the essentials at hand.
Automatic Semicolons
return on Its Own Line
function f() {
return
{ ok: true };
}
f() // undefined
// Fix: start the value on the same line
function g() {
return { ok: true };
}
g() // { ok: true }
Plus & Dates
Plus Joins Text
"1" + 2 // "12"
"5" - 1 // 4
1 + 2 + "3" // "33"
// Fix: convert first
Number("1") + 2 // 3
Months Start at 0
const d = new Date(2026, 0, 15);
d.getMonth() // 0 (January)
d.getDate() // 15
Frozen & Holey
freeze() Is Shallow
const c = Object.freeze({ a: { b: 1 } });
c.a.b = 2; // Allowed!
c // { a: { b: 2 } }
// Fix: structuredClone, or freeze deep
delete Leaves a Hole
const a = [1, 2, 3];
delete a[1]; // [1, <empty>, 3]
a.length // 3
// Fix: remove by position
a.toSpliced(1, 1) // [1, 3]
Want to go deeper on JavaScript?
RealJS teaches the mental models that let you write, read, and review modern JavaScript with confidence, whether you or an AI wrote it.
Not sure where you stand? Take the free JavaScript Skill Test quiz to find your level.
Learn more at realjs.com 💡
You can download this information as a printable cheat sheet:
Free Bonus: JavaScript Gotchas Cheat Sheet
Get the JavaScript Gotchas Cheat Sheet (PDF), a printable reference to loose equality, typeof null, NaN, floating-point math, sort(), this binding, hoisting, reference equality, semicolon insertion, parseInt, for...in on arrays, string concatenation, Date months, shallow freeze, and array holes: