In short
These are the most common JavaScript mistakes beginners and even experienced developers make. For each one you will see the wrong code, why it breaks, and the right way to write it. Comparison mistakes: = vs ===, NaN and comparing objects Mistake 1: Using = instead of === in a condition.
These are the most common JavaScript mistakes beginners and even experienced developers make. For each one you will see the wrong code, why it breaks, and the right way to write it.
Comparison mistakes: = vs ===, NaN and comparing objects
Mistake 1: Using = instead of === in a condition. One = assigns a value. Three === compares. An assignment inside if changes your variable and is almost always "true".
// Wrong
let role = "guest";
if (role = "admin") { // this SETS role to "admin"
console.log("Welcome, admin!"); // Output: Welcome, admin! (oops)
}
// Right
let userRole = "guest";
if (userRole === "admin") {
console.log("Welcome, admin!");
} else {
console.log("Access denied"); // Output: Access denied
}
Also avoid loose ==, which converts types first: "1" == 1 and 0 == "" are both true. Use === everywhere.
Mistake 2: Checking for NaN with ===. NaN ("Not a Number") is the only value that is not equal to itself. So x === NaN is always false.
const price = Number("abc"); // NaN, because "abc" is not a number
// Wrong
console.log(price === NaN); // Output: false (always!)
console.log(isNaN("hello")); // Output: true (old isNaN converts text first)
// Right
console.log(Number.isNaN(price)); // Output: true
console.log(Number.isNaN("hello")); // Output: false (it is a string, not NaN)
Mistake 3: Comparing objects or arrays with ===. Objects are compared by reference: two objects are only equal if they are the very same object in memory, like two identical houses at different addresses.
// Wrong
console.log({ id: 1 } === { id: 1 }); // Output: false
console.log([1, 2] === [1, 2]); // Output: false
// Right: compare the values you care about
const a = { id: 1, name: "Pen" };
const b = { id: 1, name: "Pen" };
console.log(a.id === b.id); // Output: true
console.log(JSON.stringify(a) === JSON.stringify(b)); // Output: true (simple objects, same key order)
Number mistakes: money, parseInt and sort
Mistake 4: Using decimals for money. As you saw in the numbers lesson, computers store decimals in binary, so 0.1 + 0.2 is not exactly 0.3. With money, those tiny errors add up.
// Wrong
console.log(0.1 + 0.2); // Output: 0.30000000000000004
console.log(1.10 * 3); // Output: 3.3000000000000003 (three items at $1.10)
// Right: work in whole cents, format only when you show the price
const priceCents = 1999;
const totalCents = priceCents * 3; // 5997, exact
const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
console.log(money.format(totalCents / 100)); // Output: $59.97
Mistake 5: Using parseInt without a radix. The radix is the number base (10 for normal numbers). Without it, parseInt guesses from the text, and in map it gets the array index as the base by accident.
// Wrong
console.log(["10", "10", "10"].map(parseInt)); // Output: [10, NaN, 2]
console.log(parseInt("0x1A")); // Output: 26 (read as hexadecimal)
// Right
console.log(["10", "10", "10"].map((s) => parseInt(s, 10))); // Output: [10, 10, 10]
console.log(["10", "10", "10"].map(Number)); // Output: [10, 10, 10]
Mistake 6: Sorting numbers without a compare function. sort() compares items as text by default, so "100" comes before "5".
// Wrong
console.log([10, 1, 5, 100].sort()); // Output: [1, 10, 100, 5]
// Right
console.log([10, 1, 5, 100].sort((a, b) => a - b)); // Output: [1, 5, 10, 100]
Loop mistakes: off-by-one, changing arrays and var
Mistake 7: Off-by-one errors. Arrays start at index 0, so the last index is length - 1. Using <= goes one step too far.
const colors = ["red", "green", "blue"];
// Wrong
for (let i = 0; i <= colors.length; i++) console.log(colors[i]);
// Output: red, green, blue, undefined
// Right
for (let i = 0; i < colors.length; i++) console.log(colors[i]);
// Output: red, green, blue
for (const color of colors) console.log(color); // even safer: no index at all
Mistake 8: Removing items from an array while looping over it. When you splice an item, everything after it moves one place left, and the loop skips the next item.
// Wrong
const cart = ["apple", "sold-out", "sold-out", "bread"];
for (let i = 0; i < cart.length; i++) {
if (cart[i] === "sold-out") cart.splice(i, 1);
}
console.log(cart); // Output: ["apple", "sold-out", "bread"] (one was skipped)
// Right: build a new array with filter
const basket = ["apple", "sold-out", "sold-out", "bread"];
const available = basket.filter((item) => item !== "sold-out");
console.log(available); // Output: ["apple", "bread"]
Mistake 9: Using var in a loop with callbacks. var creates one shared variable for the whole function. By the time the timers run, the loop has finished and i is 3. let creates a fresh i for each round of the loop.
// Wrong
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3
// Right
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// Output: 0, 1, 2
Function mistakes: missing await, arrow returns and this
Mistake 10: Forgetting await. Without await, you get a Promise (a "box that will contain the answer later"), not the answer itself.
// Wrong
async function loadUser() {
const response = fetch("https://jsonplaceholder.typicode.com/users/1");
console.log(response); // Output: Promise {<pending>}
// response.json() would fail: TypeError: response.json is not a function
}
// Right
async function loadUserFixed() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await response.json();
console.log(user.name); // Output: Leanne Graham
}
loadUser();
loadUserFixed();
Mistake 11: Forgetting return in an arrow function with braces. An arrow function returns automatically only without braces. With { }, you must write return. To return an object directly, wrap it in parentheses.
const nums = [1, 2, 3];
// Wrong
console.log(nums.map((n) => { n * 2 })); // Output: [undefined, undefined, undefined]
console.log(nums.map((n) => { value: n })); // Output: [undefined, undefined, undefined]
// Right
console.log(nums.map((n) => n * 2)); // Output: [2, 4, 6]
console.log(nums.map((n) => { return n * 2; })); // Output: [2, 4, 6]
console.log(nums.map((n) => ({ value: n }))); // Output: [{value: 1}, {value: 2}, {value: 3}]
Mistake 12: Losing this in a callback. A normal function passed as a callback gets its own this, which is not your object. Arrow functions keep the this of the place where they were written.
// Wrong
const timer = {
seconds: 0,
start() {
setTimeout(function () {
this.seconds++; // "this" is not timer here
console.log(this.seconds); // Output: NaN
}, 100);
}
};
timer.start();
// Right
const stopwatch = {
seconds: 0,
start() {
setTimeout(() => {
this.seconds++; // arrow function: "this" is still stopwatch
console.log(this.seconds); // Output: 1
}, 100);
}
};
stopwatch.start();
Security mistake: innerHTML with user input
Mistake 13: Putting user input into innerHTML. innerHTML turns text into real HTML. If a user types HTML with JavaScript inside it, that code runs on your page. This attack is called XSS (cross-site scripting), and it can steal your users' data.
<input id="comment">
<button id="post">Post comment</button>
<p id="wrong"></p>
<p id="right"></p>
<script>
// Pretend an attacker typed this into the comment box
document.getElementById("comment").value = `<img src="x" onerror="alert('hacked')">`;
document.getElementById("post").addEventListener("click", () => {
const text = document.getElementById("comment").value;
// Wrong: the browser runs the onerror code and shows "hacked"
document.getElementById("wrong").innerHTML = text;
// Right: the text is shown exactly as typed, nothing runs
document.getElementById("right").textContent = text;
});
</script>
When you click the button, the "wrong" paragraph runs the attacker's code and an alert pops up. The "right" paragraph simply shows the text. Use textContent for any text that came from a user, a URL or an API. If you must build HTML, create elements with document.createElement.
Tip: ESLint catches several of these mistakes for you, including
=in conditions (no-cond-assign),==(eqeqeq),x === NaN(use-isnan) and missingawaitpatterns. Turn it on in every project.
| Mistake | Quick fix |
|---|---|
= in a condition | Use === |
x === NaN | Number.isNaN(x) |
obj1 === obj2 for content | Compare the properties |
| Decimals for money | Use whole cents and Intl.NumberFormat |
parseInt(s) | parseInt(s, 10) or Number(s) |
numbers.sort() | numbers.sort((a, b) => a - b) |
i <= arr.length | i < arr.length or for...of |
splice inside a loop | filter() |
var in a loop | let |
No await | await every promise you need the result of |
{ } arrow with no return | Remove braces or add return |
function callback losing this | Use an arrow function |
innerHTML = userText | textContent = userText |
Practice exercise
This code has four mistakes. Find and fix them: const scores = ["40", "9", "100"]; const nums = scores.map(parseInt); const sorted = nums.sort(); for (let i = 0; i <= sorted.length; i++) { if (sorted[i] = 100) console.log("Perfect score!"); }. The fixed version should sort the numbers as [9, 40, 100] and print "Perfect score!" exactly once.
Hint: look for the parseInt radix, the sort compare function, the loop condition and the comparison operator.
Key takeaways
- Use
===for comparisons,Number.isNaN()for NaN, and compare objects by their properties. - Store money in whole cents, pass a radix to
parseInt, and givesort()a compare function for numbers. - Use
<withlength,filter()instead of removing items in a loop, andletinstead ofvar. - Remember
await, addreturnin arrow functions with braces, and use arrow callbacks to keepthis. - Never put user input into
innerHTML; usetextContentinstead.