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:

JavaScript Gotchas Cheat Sheet

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 !==
Language: JavaScript Filename: Loose Equality Surprises
0 == ""            // true
"0" == false       // true
[] == false        // true
null == 0          // false
null == undefined  // true
Language: JavaScript Filename: Fix: Use Strict Equality
0 === ""           // false
"0" === false      // false
value == null      // OK: null or undefined
Language: JavaScript Filename: typeof null
typeof null        // "object" (a bug)
// Fix: test for null directly
value === null     // true if null

NaN

  • NaN is the only value not equal to itself
  • Global isNaN() converts first
Language: JavaScript Filename: 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
Language: JavaScript Filename: The Problem
0.1 + 0.2           // 0.30000000000000004
0.1 + 0.2 === 0.3   // false
1.005.toFixed(2)    // "1.00"
Language: JavaScript Filename: 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()
Language: JavaScript Filename: sort() Without a Comparator
[10, 9, 1].sort()   // [1, 10, 9]
Language: JavaScript Filename: Fix: Compare Numbers
[10, 9, 1].toSorted((a, b) => a - b)
// [1, 9, 10]
Language: JavaScript Filename: parseInt Quirks
parseInt("12px")        // 12
parseInt("0.5")         // 0
parseInt(0.0000005)     // 5
["1", "2", "3"].map(parseInt)
// [1, NaN, NaN]
Language: JavaScript Filename: Fix: Be Explicit
parseInt("ff", 16)      // 255
["1", "2", "3"].map(Number)
// [1, 2, 3]
Number("12px")          // NaN

Comparing by Reference

  • Objects are equal only if they are the same object
Language: JavaScript Filename: 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

Language: JavaScript Filename: 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

  • this depends on how a function is called, not where it is written
  • Arrow functions use the this around them
  • Same results in scripts and ES modules; this?. avoids a module TypeError
Language: JavaScript Filename: Lost Method Reference
const user = {
  name: "Ada",
  hi() { return this?.name; },
};
user.hi()            // "Ada"
const hi = user.hi;
hi()                 // undefined
Language: JavaScript Filename: Fix: bind() or a Wrapper
user.hi.bind(user)()   // "Ada"
const run = () => user.hi();
run()                  // "Ada"
Language: JavaScript Filename: 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

  • var is hoisted and starts as undefined
  • let and const exist but can’t be touched before their line
Language: JavaScript Filename: Same Name, Different Failures
typeof v     // "undefined"
var v = 1;

l            // ReferenceError (TDZ)
let l = 2;

Automatic Semicolons

Language: JavaScript Filename: 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

Language: JavaScript Filename: Plus Joins Text
"1" + 2          // "12"
"5" - 1          // 4
1 + 2 + "3"      // "33"
// Fix: convert first
Number("1") + 2  // 3
Language: JavaScript Filename: Months Start at 0
const d = new Date(2026, 0, 15);
d.getMonth()     // 0 (January)
d.getDate()      // 15

Frozen & Holey

Language: JavaScript Filename: freeze() Is Shallow
const c = Object.freeze({ a: { b: 1 } });
c.a.b = 2;       // Allowed!
c                // { a: { b: 2 } }
// Fix: structuredClone, or freeze deep
Language: JavaScript Filename: 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]

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:

JavaScript Gotchas Cheat Sheet