🧠 Understanding Memory Leaks in JavaScript – With Real-World Examples & Solutions


If you're a JavaScript developer, you might have heard the term "memory leak" tossed around in performance discussions. But what exactly is a memory leak? How does it happen in JavaScript, and more importantly, how can you avoid it?
In this blog post, we'll break down the concept of memory leaks in simple, beginner-friendly language, walk through common scenarios, and explore real-world solutions to fix and prevent them.
📦 What is a Memory Leak?
A memory leak happens when your application uses memory but never releases it — even when it's no longer needed. Over time, this causes your application to consume more and more memory, eventually slowing down or crashing.
JavaScript is a garbage-collected language, which means the JS engine automatically frees memory that's no longer reachable. However, this only works if you don't leave accidental references to data you no longer use.
🔎 How JavaScript Memory Works
JavaScript memory is managed in two main parts:
- Stack Memory – for primitive values (like numbers and strings).
- Heap Memory – for objects, arrays, functions, and anything non-primitive.
The garbage collector periodically scans the heap and cleans up any data that can’t be reached anymore. A memory leak happens when some object remains reachable even though your program no longer needs it.
⚠️ Common Causes of Memory Leaks in JavaScript
Let’s walk through real examples that cause memory leaks — and how to fix them.
1. ❌ Accidental Global Variables
function startLeak() {
leakedVar = "I'm a global variable"; // forgot to use let/const
}
startLeak();
What's happening:
leakedVar
is declared withoutlet
,const
, orvar
, so it's automatically attached to the globalwindow
object.- It stays in memory until the page reloads.
✅ Solution: Always use let
, const
, or var
to declare variables.
function startLeak() {
const leakedVar = "I'm local now!";
}
2. ❌ Closures Holding on to Large Variables
function outer() {
let largeArray = new Array(1000000).fill("data");
return function inner() {
console.log("Using closure");
};
}
const hold = outer();
What's happening:
inner
is returned and stored inhold
, keeping a reference toouter
's scope.- The huge
largeArray
is never collected, even if it’s unused.
✅ Solution: Avoid unnecessary closures or manually dereference large unused variables.
function outer() {
let largeArray = new Array(1000000).fill("data");
function inner() {
console.log("Using closure");
}
largeArray = null; // allow garbage collection
return inner;
}
3. ❌ Detached DOM Elements
let element = document.getElementById("myDiv");
document.body.removeChild(element);
// Still holding reference
let ref = element;
What's happening:
- You've removed the element from the DOM, but a reference still exists (
ref
), so the memory isn’t freed.
✅ Solution: Nullify references once DOM elements are removed.
document.body.removeChild(element);
element = null;
4. ❌ Unstopped Timers or Intervals
function startTimer() {
let username = "Kuntal";
setInterval(() => {
console.log(`Hi ${username}`);
}, 1000);
}
startTimer();
What's happening:
setInterval
holds a reference tousername
forever unless you callclearInterval()
.
✅ Solution: Always clean up intervals and timeouts when no longer needed.
const timerId = setInterval(() => {
console.log("Running...");
}, 1000);
// Later
clearInterval(timerId);
In frameworks like React, clean intervals in useEffect
:
useEffect(() => {
const id = setInterval(() => console.log("tick"), 1000);
return () => clearInterval(id); // cleanup
}, []);
5. ❌ Forgotten Event Listeners
const button = document.getElementById("clickMe");
function handleClick() {
console.log("Clicked");
}
button.addEventListener("click", handleClick);
// Later: DOM node removed
button.remove(); // But event listener still holds reference
What's happening:
- The event listener holds a reference to
button
, even though it's no longer in the DOM.
✅ Solution: Remove event listeners before removing DOM nodes.
button.removeEventListener("click", handleClick);
button.remove();
6. ❌ Caching Data Without Limits
const cache = {};
function cacheData(key, value) {
cache[key] = value; // No limit on cache size
}
function getCachedData(key) {
return cache[key];
}
// Usage
cacheData("largeData", new Array(1000000).fill("data"));
getCachedData("largeData");
What's happening:
- The
cache
object grows indefinitely, consuming more memory as you cache more data.
✅ Solution: Implement a cache eviction strategy, like LRU (Least Recently Used) or use WeakMap
for temporary data.
Note: A WeakMap
in JavaScript is a collection of key-value pairs where the keys must be objects and the values can be of any data type. Its primary distinction from a regular Map is that its keys are "weakly referenced." This means that if an object used as a key in a WeakMap
is no longer referenced anywhere else in the application, it becomes eligible for garbage collection, and its corresponding entry in the WeakMap
will also be removed.
const cache = new WeakMap();
function cacheData(key, value) {
cache.set(key, value);
}
function getCachedData(key) {
return cache.get(key);
}
// Usage
const key = {}; // must be an object
cacheData(key, new Array(1000000).fill("data"));
getCachedData(key);
Note: WeakMap
keys must be objects, not primitives like strings.
🛠️ Real-World Example: Memory Leak in Action
✅ HTML + JavaScript Demo (Browser Memory Leak)
You can run this in your browser or tools like JSFiddle, CodePen, or just paste it in your browser console inside a test HTML page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Memory Leak Demo</title>
</head>
<body>
<div id="container">
<button id="add">Add Leaky Element</button>
<button id="cleanup">Clean References</button>
</div>
<script>
// This array will simulate a memory leak
const leakyStorage = [];
document.getElementById("add").addEventListener("click", () => {
const div = document.createElement("div");
div.textContent = "Leaky div " + new Date().toLocaleTimeString();
document.body.appendChild(div);
// Remove from DOM, but still hold reference in array
document.body.removeChild(div);
leakyStorage.push(div); // Memory leak!
});
document.getElementById("cleanup").addEventListener("click", () => {
// Fix the leak: remove references
leakyStorage.length = 0;
console.log("Memory cleaned: references removed!");
});
</script>
</body>
</html>
🧠 How This Demo Leaks Memory
- When you click Add Leaky Element, it creates a new
<div>
, adds it to the DOM, then removes it. - But the removed element is still stored in the
leakyStorage
array, keeping it in memory! - Click this multiple times → memory usage grows unnecessarily.
✅ Clicking Clean References clears the array and allows garbage collection to free that memory.
🧪 How to Observe This
- Open Chrome DevTools → Memory tab.
- Click “Add Leaky Element” several times.
- Take a heap snapshot.
- Click “Clean References”.
- Take another heap snapshot.
- You should see the nodes are now eligible for garbage collection.
🧪 Node.js Memory Leak Example (In-Memory Cache)
📁 File: leaky-server.js
const http = require("http");
// Simulate in-memory cache (the leak)
const memoryLeakCache = {};
let counter = 0;
const server = http.createServer((req, res) => {
if (req.url === "/leak") {
const bigData = Buffer.alloc(5 * 1024 * 1024, "a"); // 5MB of data
// Never deleting old data — memory grows forever
memoryLeakCache[counter++] = bigData;
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(`Added 5MB data. Total keys: ${counter}\n`);
} else if (req.url === "/status") {
const used = process.memoryUsage();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(used, null, 2));
} else {
res.writeHead(404);
res.end("Not Found");
}
});
server.listen(3000, () => {
console.log("Server running at http://localhost:3000");
});
▶️ How to Run This Demo
- Save the above code as
leaky-server.js
. Run it:
node leaky-server.js
In your browser or via
curl
, hit:http://localhost:3000/leak
repeatedly (to simulate leaks)http://localhost:3000/status
(to monitor memory usage)
Example:
curl http://localhost:3000/leak curl http://localhost:3000/status
🧠 What’s Leaking?
Every time you hit /leak
, the server stores 5MB of data in memoryLeakCache
without removing old entries. Over time, memory usage grows, and garbage collection can't free anything since all objects are still referenced.
✅ How to Fix It (Safe Cache Pattern)
Here’s a safe version with a cache limit:
const memorySafeCache = {};
const MAX_ENTRIES = 100;
function addToCache(key, value) {
if (Object.keys(memorySafeCache).length >= MAX_ENTRIES) {
const oldestKey = Object.keys(memorySafeCache)[0];
delete memorySafeCache[oldestKey]; // clear old entries
}
memorySafeCache[key] = value;
}
// Or use a Map for better performance
const memorySafeCache = new Map();
const MAX_ENTRIES = 100;
function addToCache(key, value) {
if (memorySafeCache.size >= MAX_ENTRIES) {
const oldestKey = memorySafeCache.keys().next().value;
memorySafeCache.delete(oldestKey);
}
memorySafeCache.set(key, value);
}
Or better yet, use:
lru-cache
(a memory-safe LRU implementation)- external cache like Redis for larger datasets
🛠 Bonus: Monitor Memory with CLI
Use top
, htop
, or Node.js built-ins:
node --inspect leaky-server.js
Then open Chrome at chrome://inspect
→ Memory tab.
🧹 Best Practices to Prevent Memory Leaks
Here are habits you should adopt to keep your app memory-efficient:
- ✅ Always declare variables with
let
,const
, orvar
- ✅ Remove event listeners before removing DOM elements
- ✅ Clear all timers/intervals before navigating away
- ✅ Avoid holding large data in closures
- ✅ Use
WeakMap
orWeakSet
when storing DOM nodes or cache data
🔧 How to Detect Memory Leaks
Use browser dev tools like Chrome DevTools:
- Go to Performance > Memory tab
- Record a heap snapshot
- Interact with your app
- Take another snapshot
- Look for objects that should’ve been removed but still exist
You can also use tools like:
- Lighthouse
- Profiler tab in Chrome DevTools
- performance.memory API
🧾 Conclusion
Memory leaks can be silent performance killers in JavaScript apps — especially SPAs, where the page never reloads. By understanding how JavaScript manages memory and following best practices like cleaning up intervals, event listeners, and DOM references, you can keep your application fast, efficient, and bug-free.
Now that you know what a memory leak is and how to fix it, you’re already ahead of many developers who only hear the term without truly understanding it.
✍️ Author’s Note
I hope this beginner-friendly guide helps you grasp the concept of memory leaks in JavaScript. If you have any questions or want to share your experiences with memory leaks, feel free to leave a comment below!
💬 Have Questions or Suggestions?
Drop a comment below or connect with me on LinkedIn or GitHub. Let’s make apps faster together! 🚀
Subscribe to my newsletter
Read articles from KUNTAL MAITY directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

KUNTAL MAITY
KUNTAL MAITY
I’m a passionate Full-Stack Developer who loves building performance-driven web and mobile applications. I work primarily with JavaScript, React, Next.js, Node.js, and MongoDB, and I enjoy simplifying complex concepts into developer-friendly tutorials. On this blog, you'll find hands-on guides, real-world projects, and developer insights—all aimed at helping you level up your coding skills and build production-ready apps. Whether you're into backend performance, frontend polish, or full-stack architecture, there's something here for you! Let’s learn, build, and grow together. 💻✨