1 Using == instead of ===
✗ COMMON MISTAKE
if (age == "18") {
  console.log("Adult");
}
✓ BETTER
if (age === 18) {
  console.log("Adult");
}

Why: == coerces types before comparing, so "18" == 18 is true. === compares value and type, avoiding surprising bugs.

2 Thinking `const` makes objects immutable
✗ COMMON MISTAKE
const user = { name: "Alice" };
// "this can't change, it's const!"
user.name = "Bob"; // ...but it does
✓ BETTER
const user = Object.freeze({ name: "Alice" });
user.name = "Bob"; // silently ignored (fails in strict mode)

Why: const only locks the variable binding, not the object's contents. Use Object.freeze() if you need real immutability.

3 Losing `this` inside a callback
✗ COMMON MISTAKE
const timer = {
  seconds: 0,
  start() {
    setInterval(function () {
      this.seconds++; // `this` is not `timer` here
    }, 1000);
  }
};
✓ BETTER
const timer = {
  seconds: 0,
  start() {
    setInterval(() => {
      this.seconds++; // arrow fn keeps `this`
    }, 1000);
  }
};

Why: Regular function expressions get their own this. Arrow functions inherit this from where they're defined.

4 Mutating an array while relying on its old reference
✗ COMMON MISTAKE
let cart = ["pen"];
function addItem(items) {
  items.push("book"); // mutates the original
}
addItem(cart);
✓ BETTER
let cart = ["pen"];
function addItem(items) {
  return [...items, "book"]; // returns a new array
}
cart = addItem(cart);

Why: Arrays are reference types — passing one into a function and mutating it changes the original everywhere else it's used. Prefer returning new arrays.

5 Forgetting `await` on a Promise
✗ COMMON MISTAKE
async function getUser() {
  const res = fetch("/api/user"); // missing await
  console.log(res.json); // undefined
}
✓ BETTER
async function getUser() {
  const res = await fetch("/api/user");
  const data = await res.json();
  console.log(data);
}

Why: Without await, res is a pending Promise object, not the resolved value — a very common source of undefined bugs.

6 Comparing floating-point numbers directly
✗ COMMON MISTAKE
console.log(0.1 + 0.2 === 0.3); // false
✓ BETTER
const isClose = Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON;
console.log(isClose); // true

Why: Floating-point math can't represent 0.1 or 0.2 exactly in binary, so tiny rounding errors creep in. Compare with a small tolerance instead of ===.

7 Not returning a value inside `.map()`
✗ COMMON MISTAKE
const doubled = [1, 2, 3].map((n) => {
  n * 2; // no return — result is undefined
});
✓ BETTER
const doubled = [1, 2, 3].map((n) => n * 2); // implicit return

Why: Arrow functions with a block body { } need an explicit return. Without it, every element becomes undefined.

8 Leaking variables into the global scope
✗ COMMON MISTAKE
function setName() {
  username = "Alice"; // no let/const/var
}
setName();
console.log(username); // works — but shouldn't
✓ BETTER
function setName() {
  let username = "Alice";
}
setName();
console.log(username); // ReferenceError (correct)

Why: Forgetting a declaration keyword silently creates a global variable in non-strict mode, which can clash with other code. Always declare with let/const.

9 Not handling rejected Promises
✗ COMMON MISTAKE
fetch("/api/data")
  .then((res) => res.json())
  .then((data) => render(data));
// no .catch — errors vanish silently
✓ BETTER
fetch("/api/data")
  .then((res) => res.json())
  .then((data) => render(data))
  .catch((err) => showError(err));

Why: An unhandled rejected Promise fails silently in the console (or crashes in Node). Always add a .catch() or wrap await in try/catch.

10 Off-by-one errors in loops
✗ COMMON MISTAKE
const items = ["a", "b", "c"];
for (let i = 1; i <= items.length; i++) {
  console.log(items[i]); // skips "a", ends undefined
}
✓ BETTER
const items = ["a", "b", "c"];
for (let i = 0; i < items.length; i++) {
  console.log(items[i]);
}

Why: Array indices are zero-based and the last valid index is length - 1. Starting at 1 or using <= reads past the end.