DEV Community

Cover image for How JavaScript Handles Memory Under the Hood — A Complete Deep Dive
Ahmed Niazy
Ahmed Niazy

Posted on

How JavaScript Handles Memory Under the Hood — A Complete Deep Dive

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
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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" };
Enter fullscreen mode Exit fullscreen mode

Stack memory holds:

a → 5
b → 5
obj → (pointer to heap)
Enter fullscreen mode Exit fullscreen mode

Heap memory holds:

{ name: "Alaa" }
Enter fullscreen mode Exit fullscreen mode

🔥 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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

🔄 5. Memory Lifecycle: Allocation → Usage → Release

1. Allocation

JS allocates memory automatically:

let x = 10;        // primitive → stack
let obj = {};      // reference → stack, object → heap
Enter fullscreen mode Exit fullscreen mode

2. Usage

JS reads or modifies values:

obj.name = "Alaa";
Enter fullscreen mode Exit fullscreen mode

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) ❌
Enter fullscreen mode Exit fullscreen mode

✔ 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();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

If you never call:

clearInterval(id);
Enter fullscreen mode Exit fullscreen mode

→ 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"));
Enter fullscreen mode Exit fullscreen mode

If you remove the element:

btn.remove();
Enter fullscreen mode Exit fullscreen mode

The listener STILL exists unless removed:

btn.removeEventListener("click", handler);
Enter fullscreen mode Exit fullscreen mode

🔥 8.4 Leaks from Global Variables

Common mistake:

leaked = "Hello"; // becomes window.leaked → never removed
Enter fullscreen mode Exit fullscreen mode

🔥 8.5 Leaks from Forgotten DOM References

let div = document.getElementById("box");

document.body.removeChild(div);

// but:
console.log(div); // reference keeps it alive!
Enter fullscreen mode Exit fullscreen mode

🔥 8.6 Leaks from Caches, Maps, WeakMaps

const cache = new Map();

function process(obj) {
  cache.set(obj, "processed");
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Take snapshot
  2. Click around the page
  3. Take another snapshot
  4. Heap should return to the same level
  5. 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
}, []);
Enter fullscreen mode Exit fullscreen mode

If cleanup is missing → leak.


🛠️ 11. Real-World Example: Node.js Memory Leak

let cache = {};

function addUser(user) {
  cache[user.id] = user; // never removed
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

🏁 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)