Common JavaScript Mistakes and How to Fix Them
JavaScript is a powerful yet quirky language, and even experienced developers make mistakes. Whether you're a beginner or a seasoned coder, avoiding these common pitfalls will save you hours of debugging.
In this article, I’ll cover 10 frequent JavaScript mistakes and how to fix them—with practical examples and best practices. Let’s dive in!
Table of Contents
- 1. Using
==
Instead of===
- 2. Not Handling
undefined
ornull
- 3. Modifying Objects While Iterating
- 4. Ignoring Asynchronous Behavior
- 5. Memory Leaks with Event Listeners
- 6. Misusing
this
in Callbacks - 7. Blocking the Event Loop
- 8. Not Catching Promise Rejections
- 9. Mutating State Directly (React/Angular/Vue)
- 10. Overusing
var
Instead oflet
/const
- Final Tips to Avoid JS Mistakes
1. Using ==
Instead of ===
The Mistake\
Using loose equality (==
) can lead to unexpected type coercion:
console.log(5 == "5"); // true (WAT?!)
The Fix\
Always use strict equality (===
) to compare both value and type:
console.log(5 === "5"); // false (correct)
2. Not Handling undefined
or null
The Mistake\
Assuming a variable exists without checks:
function greet(user) {
return "Hello, " + user.name; // 💥 if user is undefined
}
The Fix\
Use optional chaining (?.
) or default values:
function greet(user) {
return "Hello, " + (user?.name ?? "Guest");
}
3. Modifying Objects While Iterating
The Mistake\
Modifying an array while looping can cause bugs:
const users = ["Alice", "Bob", "Charlie"];
users.forEach(user => {
if (user === "Bob") users.shift(); // Mutates array mid-loop
});
The Fix\
Create a copy of the array first:
[...users].forEach(user => {
/* safe iteration */
});
4. Ignoring Asynchronous Behavior
The Mistake\
Assuming async code runs sequentially:
let data;
fetch("/api/data").then(res => data = res);
console.log(data); // undefined 😱
The Fix\
Use async/await
for cleaner async handling:
async function loadData() {
const data = await fetch("/api/data");
console.log(data); // Works!
}
5. Memory Leaks with Event Listeners
The Mistake\
Forgetting to remove event listeners:
button.addEventListener("click", onClick);
// Later...
button.remove(); // Listener still in memory!
The Fix\
Always clean up listeners:
button.addEventListener("click", onClick);
// On removal:
button.removeEventListener("click", onClick);
6. Misusing this
in Callbacks
The Mistake\
Losing this
context in callbacks:
class User {
constructor(name) { this.name = name; }
greet() {
setTimeout(function() {
console.log("Hello, " + this.name); // 😵 `this` is `window`!
}, 1000);
}
}
The Fix\
Use arrow functions (they don’t rebind this
):
setTimeout(() => console.log("Hello, " + this.name), 1000);
7. Blocking the Event Loop
The Mistake\
Running CPU-heavy tasks synchronously:
function processLargeData() {
// Blocks UI for seconds ⏳
for (let i = 0; i < 1e9; i++);
}
The Fix\
Use Web Workers or chunk tasks:
setTimeout(() => processChunk(), 0); // Yields to the event loop
8. Not Catching Promise Rejections
The Mistake\
Unhandled promise rejections crash Node.js apps:
fetch("/api").then(res => res.json()); // No .catch()!
The Fix\
Always handle errors:
fetch("/api")
.then(res => res.json())
.catch(err => console.error("API failed:", err));
9. Mutating State Directly (React/Angular/Vue)
The Mistake\
Changing state directly in frameworks:
const [users, setUsers] = useState([]);
users.push("Alice"); // 🚫 Doesn’t trigger re-render
The Fix\
Always use immutable updates:
setUsers([...users, "Alice"]); // ✅ Correct
10. Overusing var
Instead of let
/const
The Mistake\
var
has function scope, leading to bugs:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Logs 3, 3, 3 😬
}
The Fix\
Use let
(block-scoped):
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Logs 0, 1, 2 ✅
}
Final Tips to Avoid JS Mistakes
✔ Use TypeScript for static type checking.\
✔ Enable ESLint to catch errors early.\
✔ Write tests (Jest, Mocha) to prevent regressions.\
✔ Read MDN Docs when unsure about a method.