JS

Common Mistakes

JavaScript · 8 entries

== vs === Confusion

syntax
// Wrong: == with type coercion
// Right: === for strict comparison
example
// These all evaluate to true (unexpected!)
console.log("" == false);    // true
console.log(0 == "");        // true
console.log(null == undefined); // true
console.log("0" == false);   // true

// Use strict equality
console.log("" === false);   // false
console.log(0 === "");       // false
console.log(null === undefined); // false

Note Rule of thumb: always use === and !==. The only acceptable == use is value == null to check both null and undefined at once.

Losing 'this' Context

syntax
// Problem: extracting a method loses its 'this'
// Fix: use bind(), arrow function, or keep method call
example
class Timer {
  count = 0;

  // BUG: 'this' is undefined in the callback
  startBroken() {
    setInterval(function () {
      this.count++; // TypeError!
    }, 1000);
  }

  // FIX 1: arrow function inherits 'this'
  startFixed() {
    setInterval(() => {
      this.count++;
      console.log(this.count);
    }, 1000);
  }
}

// FIX 2: bind the method
const timer = new Timer();
const increment = timer.startFixed.bind(timer);

Note Arrow functions, .bind(), or storing this in a variable (const self = this) all solve this. Arrow functions are the cleanest modern solution.

Closure in Loops (var trap)

syntax
// Bug: var is function-scoped
// Fix: use let (block-scoped)
example
// BUG: all callbacks log 3
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3

// FIX: let is block-scoped, each iteration gets its own i
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2

Note With var, all iterations share the same variable. By the time callbacks run, the loop is done and i has its final value. This is the #1 reason to use let.

Floating Point Precision

syntax
// 0.1 + 0.2 !== 0.3
example
console.log(0.1 + 0.2);         // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

// FIX 1: Compare with tolerance
function nearlyEqual(a, b, epsilon = Number.EPSILON) {
  return Math.abs(a - b) < epsilon;
}
console.log(nearlyEqual(0.1 + 0.2, 0.3)); // true

// FIX 2: Work in integers (cents instead of dollars)
const priceInCents = 199; // $1.99
const total = priceInCents * 3; // 597 cents = $5.97

Note All numbers in JavaScript are 64-bit floating point (IEEE 754). For financial calculations, use integer arithmetic (cents) or a decimal library.

Unexpected Array Mutation

syntax
// Mutating: sort, reverse, splice, push, pop, shift, unshift
// Non-mutating: toSorted, toReversed, toSpliced, with, map, filter, slice
example
const original = [3, 1, 2];
const sorted = original.sort(); // BUG: mutates original!
console.log(original); // [1, 2, 3] (changed!)
console.log(sorted === original); // true (same reference!)

// FIX 1: Copy first
const safeSort = [...original].sort();

// FIX 2: Use non-mutating method (ES2023)
const safeSorted = original.toSorted();

Note sort(), reverse(), splice() all mutate. This is a major bug source especially in React/Vue state. Use toSorted(), toReversed(), toSpliced() for safe alternatives.

async/await with forEach (Does Not Work)

syntax
// BUG: forEach ignores async callbacks
// FIX: use for...of or Promise.all with map
example
const userIds = [1, 2, 3];

// BUG: forEach does NOT await each call
userIds.forEach(async (id) => {
  const user = await fetch(`/api/users/${id}`);
  console.log(await user.json()); // fires unpredictably
});

// FIX 1: Sequential processing
for (const id of userIds) {
  const user = await fetch(`/api/users/${id}`);
  console.log(await user.json());
}

// FIX 2: Parallel processing
const users = await Promise.all(
  userIds.map(id => fetch(`/api/users/${id}`).then(r => r.json()))
);

Note forEach returns undefined and does not await the callback. The loop completes immediately while async callbacks run in the background with no error handling.

Object/Array Reference Sharing

syntax
// Assigning objects copies the reference, not the value
example
const defaults = { theme: "light", notifications: { email: true, sms: false } };

// BUG: both point to the same object
const userSettings = defaults;
userSettings.theme = "dark";
console.log(defaults.theme); // "dark" (changed!)

// FIX: shallow copy
const settings = { ...defaults };

// FIX: deep copy (for nested objects)
const deepSettings = structuredClone(defaults);

Note Assignment (=) with objects copies the reference, not the data. Spread (...) is only a shallow copy. Use structuredClone() for nested structures.

typeof null and Other Gotchas

syntax
typeof null === "object"  // true (historic bug)
typeof NaN === "number"   // true (NaN is a number type)
typeof [] === "object"    // true (arrays are objects)
example
// All of these are surprising but correct
console.log(typeof null);        // "object"
console.log(typeof NaN);         // "number"
console.log(typeof []);          // "object"
console.log(typeof function(){}); // "function"

// Better checks
console.log(value === null);           // null check
console.log(Number.isNaN(value));      // NaN check
console.log(Array.isArray(value));     // array check

Note typeof is unreliable for null, arrays, and NaN. Use specialized checks: === null, Array.isArray(), Number.isNaN(), or instanceof for specific types.