JavaScript is often described as a “memory-safe” language because it includes a garbage collector, but this doesn’t mean memory problems magically disappear.
In reality, JS developers face memory leaks all the time — especially in:
- Single-Page Applications (SPA)
- Real-time dashboards
- Long-lived Node.js servers
- Complex React/Vue/Angular apps
- Components with continuous DOM updates
- Apps that keep state in memory for hours
To write robust, scalable, high-performance code, you must understand exactly how JavaScript memory works internally.
This article will take you from beginner to expert by exploring:
✔ Stack vs Heap
✔ Execution Contexts & Call Stack
✔ Primitive vs Reference values
✔ How JS allocates, uses, and releases memory
✔ Mark & Sweep garbage collection
✔ Modern garbage collectors (Generational GC, Incremental GC, Concurrent GC)
✔ All possible sources of memory leaks
✔ How to detect leaks using Chrome DevTools
✔ Advanced real-world examples
This is a complete deep dive, not just a simple explanation.
🧠 1. The Architecture of JavaScript Memory
JavaScript uses two fundamental memory regions:
┌─────────────────────────────────────┐
│ Stack │ → automatic, fast memory
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Heap │ → dynamic, slow memory
└─────────────────────────────────────┘
Let’s break them down in extreme detail.
🧱 2. Stack Memory — Fast, Predictable, Structured
The stack is a Last-In-First-Out (LIFO) memory system.
Push → Push → Push
← Pop ← Pop ← Pop
The stack stores:
✔ Primitive values
(numbers, strings, booleans, undefined, null, symbols, bigint)
✔ Function arguments
(even objects are passed as references stored in stack)
✔ Execution context
(local variables, this, scope chain)
✔ Return addresses
(where to go back after a function finishes)
✔ Pointers to heap objects
Example:
let a = 5; // stored directly in stack
let b = a; // copied by value
const obj = { name: "Alaa" };
Stack memory holds:
a → 5
b → 5
obj → (pointer to heap)
Heap memory holds:
{ name: "Alaa" }
🔥 3. Heap Memory — Dynamic, Flexible, Unpredictable
The heap is a large region used for objects, arrays, functions, closures, and anything dynamic.
Examples of heap allocation:
const user = { name: "Alaa", age: 25 }; // object → heap
const arr = [1, 2, 3]; // array → heap
function foo() {} // function → heap
The heap is:
- larger
- slower
- unstructured
- requires garbage collection
🏗️ 4. How JavaScript Executes Code (Call Stack + Memory)
To fully understand memory behavior, you must understand the JavaScript execution model.
🌀 Each function call creates an "Execution Context"
Example:
function sum(a, b) {
let result = a + b;
return result;
}
sum(3, 5);
Execution context contains:
- Lexical Environment
- Variable Environment
- this binding
- Function arguments
- Reference to outer environment
Example memory:
sum EC
├── a = 3 (stack)
├── b = 5 (stack)
└── result = 8 (stack)
🔄 5. Memory Lifecycle: Allocation → Usage → Release
1. Allocation
JS allocates memory automatically:
let x = 10; // primitive → stack
let obj = {}; // reference → stack, object → heap
2. Usage
JS reads or modifies values:
obj.name = "Alaa";
3. Release
When values become unreachable, memory should be released.
But this is where problems begin…
🧹 6. Garbage Collection (Mark & Sweep) — Explained in Depth
JavaScript engines like V8 (Chrome, Node) use Mark & Sweep garbage collection.
✔ Step 1 – Mark Phase
Garbage collector identifies “root” objects:
- Global object (
window/global) - Active functions on the call stack
- Closures
- Variables referenced in scope chain
From roots, GC recursively marks all reachable objects.
Roots
├── A (reachable)
│ └── B (reachable)
└── C (unreachable) ❌
✔ Step 2 – Sweep Phase
Unmarked objects are removed from heap.
This prevents memory from growing indefinitely.
🚀 7. Modern Garbage Collectors (Full Professional Understanding)
V8 does not use just one algorithm.
It uses multiple GC systems working together:
✔ 1. Generational Garbage Collection
Heap is divided into:
- Young generation → new small objects
- Old generation → older, long-lived objects
New objects die young → cleaned quickly.
Old objects stay → cleaned slowly.
✔ 2. Incremental GC
Large GC operations are split into smaller parts to avoid freezing the UI.
✔ 3. Concurrent GC
GC runs in parallel with the main thread.
✔ 4. Compaction
Defragments memory to reduce fragmentation.
All of this happens automatically — but you can still break it.
⚠️ 8. All Types of Memory Leaks in JavaScript (Complete Guide)
Here are the real ways your app leaks memory.
🔥 8.1 Leak from Closures Retaining Large Data
This is one of the most dangerous leaks.
function create() {
const hugeArray = new Array(1_000_000).fill("data");
return function () {
console.log("Using closure");
};
}
const fn = create();
Even though the returned function does not use hugeArray,
the closure keeps the entire scope alive → leak.
GC cannot remove it because the closure is still reachable.
Fix:
Move large data outside closure or nullify after use.
🔥 8.2 Leaks from setInterval / setTimeout
setInterval(() => {
console.log("Hi");
}, 1000);
If you never call:
clearInterval(id);
→ The closure stays in memory forever.
Even if the page changes.
🔥 8.3 Leaks from Event Listeners Not Removed
const btn = document.getElementById("btn");
btn.addEventListener("click", () => console.log("click"));
If you remove the element:
btn.remove();
The listener STILL exists unless removed:
btn.removeEventListener("click", handler);
🔥 8.4 Leaks from Global Variables
Common mistake:
leaked = "Hello"; // becomes window.leaked → never removed
🔥 8.5 Leaks from Forgotten DOM References
let div = document.getElementById("box");
document.body.removeChild(div);
// but:
console.log(div); // reference keeps it alive!
🔥 8.6 Leaks from Caches, Maps, WeakMaps
const cache = new Map();
function process(obj) {
cache.set(obj, "processed");
}
If obj should be garbage collected, it won’t be — because it’s stored in a Map.
Fix:
Use WeakMap, not Map.
const cache = new WeakMap();
WeakMap keys do NOT prevent GC.
🌐 8.7 Leaks in Single Page Applications (SPA)
SPAs often leak memory due to:
- components not fully destroyed
- listeners not removed
- intervals continuing across pages
- references stored inside global state (Vuex, Redux)
- images or canvas objects kept alive
- virtual DOM nodes referencing removed elements
Angular, Vue, React apps suffer from this daily.
🔎 9. Detecting Memory Leaks Using Chrome DevTools (Step-by-Step Master Class)
Open:
Chrome → F12 → Performance or Memory
Tool 1 — Heap Snapshot
Shows:
- detached DOM nodes
- objects kept alive by closures
- references preventing cleanup
Tool 2 — Allocation Timeline
Used to detect memory growth over time.
Tool 3 — Live Memory Graph
Used to track leak patterns.
What to Look For:
✔ Increasing heap after each user action
✔ Detached DOM nodes
✔ Event listeners not removed
✔ Large arrays in closures
✔ setInterval callbacks still active
A Real Test:
- Take snapshot
- Click around the page
- Take another snapshot
- Heap should return to the same level
- If it grows each time → leak
🛠️ 10. Real-World Example: Leaking React Component
useEffect(() => {
const id = setInterval(() => {
console.log("running");
}, 1000);
return () => clearInterval(id); // cleanup
}, []);
If cleanup is missing → leak.
🛠️ 11. Real-World Example: Node.js Memory Leak
let cache = {};
function addUser(user) {
cache[user.id] = user; // never removed
}
Over time (API server running for days), this kills memory.
Fix:
Use:
- LRU Cache
- TTL expiration
- WeakMap
🧵 12. Advanced Example: Leaking Through Closures
function setup() {
let bigObj = new Array(5_000_000).fill("hello");
return function inner() {
console.log("I keep bigObj alive");
};
}
let fn = setup();
Even if you never use bigObj, it’s still reachable.
Fix: Clean it manually
function setup() {
let bigObj = new Array(5_000_000).fill("hello");
function inner() {
console.log("I keep bigObj alive");
}
bigObj = null; // remove heavy reference
return inner;
}
🏁 Final Thoughts — You Are Now an Expert
JavaScript tries to manage memory for you — but you can absolutely break it.
Key takeaways:
- Stack → small, fast, structured
- Heap → large, dynamic, messy
- Execution context is the heart of JS memory
- Closures can accidentally keep entire scopes in memory
- setInterval, listeners, global vars → biggest leak sources
- SPAs are at high risk of memory leaks
- Chrome DevTools is your best friend
- GC is smart — but not magic

Top comments (0)