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.