🧠 Understanding Memory Leaks in JavaScript
👋 Introduction
Have you ever noticed your website getting slower the longer you use it? Or maybe the browser becomes unresponsive after some time? One possible reason for this is a memory leak. In JavaScript apps, memory leaks can sneak in quietly and make your application slow, buggy, or even cause it to crash. But do not worry, memory leaks are easy to avoid once you know how they happen!
❓ What is a Memory Leak?
A memory leak happens when your app keeps using memory for things it no longer needs. Think of it like leaving dirty dishes in your room. If you never clean them up, your room will get messier and messier. In programming, this "mess" can slow down or break your app.
Why Do Memory Leaks Happen in JavaScript?
Let’s look at the most common reasons:
1. Not Clearing Timers or Intervals
If you use functions like setInterval or setTimeout but forget to stop them, they will keep running forever, even if you leave the page or do not need them anymore. This keeps memory locked up.
// BAD: Timer keeps running even if not needed
function startTimer() {
setInterval(() => {
console.log('Tick');
}, 1000);
}
How to fix: Always clear your timers when you are done with them.
let timerId = setInterval(() => {
console.log('Tick');
}, 1000);
// Later, when you want to stop:
clearInterval(timerId);
2. Keeping Unneeded References
Sometimes, you store objects in arrays, maps, or variables and forget to remove them. As long as there is a reference to an object, JavaScript cannot free up that memory.
let elements = [];
function createElement() {
let el = document.createElement('div');
elements.push(el); // Now the div is always kept in memory!
}
How to fix: Remove references when you no longer need them.
elements = []; // Clears all references
// or
elements[index] = null; // Removes a specific one
3. Closures Holding Too Much Data
A closure is a function that remembers variables from outside its own scope. If you accidentally keep a large variable inside a closure, it will never be garbage collected.
function outer() {
let bigArray = new Array(1000000).fill('*');
return function inner() {
console.log(bigArray[0]);
};
}
let leaky = outer(); // bigArray is kept in memory by inner()
How to fix: Only capture variables you really need inside your closures.
4. Detached DOM Nodes
When you remove an element from the DOM but still keep a reference to it in your code, it stays in memory.
Recommended by LinkedIn
let div = document.createElement('div');
document.body.appendChild(div);
// Later...
document.body.removeChild(div); // Removed from page
// BUT: div variable is still in memory!
How to fix: Set references to null after removing the element.
document.body.removeChild(div);
div = null; // Now JavaScript can clean it up
5. Forgotten Event Listeners
If you add event listeners to elements (like buttons, windows, or documents) and forget to remove them when they are no longer needed, those listeners and sometimes the whole element they are attached to can stay in memory forever. This is especially true if you create and remove elements dynamically.
function addListener() {
const button = document.createElement('button');
button.textContent = 'Click me!';
document.body.appendChild(button);
// BAD: This event listener is never removed
button.addEventListener('click', () => {
alert('Button clicked!');
});
// If you remove the button from the DOM later, the listener still exists
document.body.removeChild(button);
// 'button' is still kept in memory by the event listener!
}
How to fix: Always remove event listeners when you remove elements or when you do not need them anymore.
function addAndRemoveListener() {
const button = document.createElement('button');
button.textContent = 'Click me!';
document.body.appendChild(button);
function handleClick() {
alert('Button clicked!');
}
button.addEventListener('click', handleClick);
// Later, before removing the element
button.removeEventListener('click', handleClick);
document.body.removeChild(button);
}
6. Unclosed Subscriptions (WebSocket, GraphQL, Firebase, etc.)
When your app opens a live connection like a WebSocket, a GraphQL subscription, or a Firebase listener, it uses memory to keep that connection alive. If you forget to close (unsubscribe from) it when your component unmounts or the user leaves the page, the connection stays open and keeps using memory, even though it is not needed anymore.
Example with WebSocket in React
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
const socket = new WebSocket('wss://example.com/socket');
socket.onmessage = (event) => {
// Handle messages...
};
// BAD: If you do not clean up, the connection stays open!
// Cleanup function:
return () => {
socket.close(); // Always close the socket!
};
}, []);
return <div>WebSocket Example</div>;
}
Example with Firebase
import { useEffect } from 'react';
import { onSnapshot } from "firebase/firestore";
function FirestoreComponent(ref) {
useEffect(() => {
const unsubscribe = onSnapshot(ref, (snapshot) => {
// Handle data...
});
// Always clean up subscriptions:
return () => {
unsubscribe(); // Unsubscribe when unmounting
};
}, [ref]);
return <div>Firebase Example</div>;
}
Human Explanation: Think of a live subscription as leaving a phone call open forever, even after you stop talking. If you never hang up, the line stays busy and wastes resources. Always remember to "hang up" by unsubscribing or closing connections when you do not need them anymore.
🕵️♂️ How to Detect Memory Leaks
1. Use Browser DevTools
2. Watch the Performance Graph
💡 Human Explanation
Imagine your app as a busy kitchen. If you never throw away the trash, it piles up and gets in the way. Memory leaks are like trash that never gets taken out. JavaScript has a garbage collector (like a cleaning robot), but it only throws out things nobody is using. If your code keeps references to old things, the garbage collector cannot help. That is why you have to clean up after yourself: clear timers, remove references, clean up event listeners, close subscriptions, and do not keep stuff you do not need.
🏁 Conclusion
Memory leaks can sneak into any JavaScript project. But if you know the common causes and remember to clean up after yourself, you will keep your app fast and healthy. Use browser tools to check your app’s memory, and if you see memory always going up, it is time to do some cleaning.
Thanks for sharing, Davit
Kotlin/Java Developer, Ex-Nike, Reactive, Cloud
9moDefinitely worth reading, thanks!
Good points. Worth reading 👍
This brought back memories of a bug I chased for days... turned out to be a rogue interval that wasn’t getting cleared 😭 Thought I was losing my mind lol Thanks for making this stuff actually readable. Definitely bookmarking this one.
Love this, Davit