Memory leaks are tricky. You don’t see them immediately. Your UI works fine… until it doesn’t.
A few minutes, hours, or days later -
❌ the app becomes slow
❌ scrolling lags
❌ typing delays
❌ CPU usage spikes
❌ mobile devices heat up or crash
And the worst part?
The bug is invisible. No errors. No warnings. Nothing in the console.
This blog will reveal exactly why memory leaks happen in JavaScript & React, how to avoid them, how to debug them, and real mistakes developers make in production.
Let’s dive in 👇
🧠 1. What Is a Memory Leak?
A memory leak happens when your program keeps holding onto memory it no longer needs, preventing the browser from freeing it.
JS uses automatic garbage collection - but GC can only help if memory becomes unreachable.
If something still references that memory → leak.
🔥 2. Why Memory Leaks Happen in JavaScript
JavaScript leaks commonly come from:
1️⃣ Global variables
window.cache = {};
Never cleared → stored forever.
2️⃣ Closures storing unnecessary data
function heavy() {
const big = new Array(1_000_000).fill("data");
return () => console.log(big.length);
}
The closure keeps big alive.
3️⃣ Event listeners not removed
window.addEventListener("scroll", handler);
// never removed → stays in memory
4️⃣ Timers not cleared
setInterval(() => ..., 1000);
// still running even when component unmounted
5️⃣ DOM references stored in JS
const elems = [];
elems.push(document.getElementById("box"));
// Even if 'box' is removed from DOM, elems still holds reference → leak
⚛️ 3. Memory Leaks in React - Real Reasons They Happen
React helps prevent leaks…
…but leaks still happen when side effects run incorrectly.
Here are the most common sources:
🧩 3.1 Not cleaning up useEffect
❌ Leak example:
useEffect(() => {
const id = setInterval(() => {
console.log("running...");
}, 1000);
}, []);
After component unmount → interval continues → leak.
✅ Fix:
useEffect(() => {
const id = setInterval(() => {
console.log("running...");
}, 1000);
// Cleanup: clear interval when component unmounts
return () => clearInterval(id);
}, []);
🧩 3.2 Event listeners inside components
❌ Leak:
useEffect(() => {
window.addEventListener("resize", handleResize);
}, []);
✅ Fix:
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
🧩 3.3 Async calls setting state after unmount
This is one of the MOST common leaks.
❌ Example:
useEffect(() => {
fetch("/api/user")
.then(res => res.json())
.then(data => setUser(data));
}, []);
If the user navigates away →
React tries to update state on an unmounted component → memory leak + warning.
✅ Fix:
useEffect(() => {
const controller = new AbortController();
fetch("/api/user", { signal: controller.signal })
.then(res => res.json())
.then(data => setUser(data))
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
return () => controller.abort();
}, []);
🧩 3.4 Huge arrays stored in state
React re-renders + stores stale copies.
❌ Storing large datasets in state
❌ Duplicating arrays on every update
❌ Logging huge objects inside useEffect
🔧 Fix:
- Use pagination
- Use memoization (
useMemo) for expensive computations - Avoid storing raw API responses if unnecessary
🧩 3.5 Memory Leaks with React Strict Mode
React Strict Mode intentionally runs effects twice in development.
If cleanup logic is wrong → leaks appear in dev only.
⚠️ This doesn't happen in production, but fixing cleanup issues prevents real leaks.
🧪 4. Debugging Memory Leaks - Chrome DevTools (Simplest Guide)
This is the part devs struggle with.
Here is a beginner-friendly flow:
Step 1: Open Performance Monitor
Chrome → More Tools → Performance Monitor
Track:
- JS Heap Size
- DOM Nodes
- Event Listeners
If these numbers keep increasing → leak.
Step 2: Take a Heap Snapshot
DevTools → Memory tab → Take Heap Snapshot
Then repeat after performing interactions.
Look for:
- Detached HTML elements
- Large arrays
- Retained closures
- Growing listeners
Step 3: Check Event Listeners
DevTools → Elements → Event Listeners
If the number keeps increasing on rerender → leak.
🛡 5. Preventing Memory Leaks - Best Practices
✔ Always clean up useEffect
✔ Avoid storing huge data in state
✔ Use paginated API responses
✔ Use AbortController to cancel fetch
✔ Throttle expensive events
✔ Use profiling tools regularly
🧨 6. Real-World Example - A Leak That Happened in Production
- A dashboard had multiple charts
- Each chart added a
resizelistener - Rerender recreated listeners
- Old listeners remained
- 50+ listeners running simultaneously
- Browser froze after 10 minutes
Fix: cleanup logic in useEffect + throttling resize events.
🏁 Final Thoughts
Memory leaks are scary because you don’t notice them immediately - but the impact is real.
React apps slow down, mobile users suffer, and debugging becomes painful.
But the good news?
👉 Once you understand how leaks happen
👉 And how to avoid them
👉 You can prevent 95% of them permanently
Your UI becomes faster, cleaner, smoother - and much easier to maintain.
Top comments (0)