Call Stack & Memory Model (Stack vs Heap)

0/12 in this phase0/57 across the roadmap

📖 Concept

Understanding how the call stack and memory work is essential for debugging, writing efficient code, and understanding recursion.

The Call Stack:

  • A LIFO (Last In, First Out) data structure managed by the JavaScript engine
  • Every function call pushes a stack frame containing: local variables, arguments, return address
  • When a function returns, its frame is popped off the stack
  • JavaScript is single-threaded — one call stack, one thing at a time

Stack vs Heap:

  • Stack memory: Fast, automatic, limited size. Stores primitives and function references. LIFO order. Auto-cleaned when function returns.
  • Heap memory: Larger, slower, manually collected (by GC). Stores objects, arrays, closures. No particular order. Cleaned by garbage collector.

What goes where?

Stack:                    Heap:
├─ number: 42             ├─ { name: "Alice", age: 30 }
├─ boolean: true          ├─ [1, 2, 3, 4, 5]
├─ string ref → ──────────├─ "Hello World"
├─ object ref → ──────────├─ function() { ... }
├─ return address         └─ closure scope
└─ ...

Stack Overflow: When the call stack exceeds its maximum size (usually 10,000-25,000 frames). Common cause: infinite recursion.

Garbage Collection: JavaScript uses mark-and-sweep — starting from root references, it marks all reachable objects and sweeps (deletes) unreachable ones. Understanding this helps avoid memory leaks.

Memory Leaks in JavaScript:

  1. Global variables that never get cleaned
  2. Forgotten event listeners
  3. Closures that hold references to large objects
  4. Detached DOM nodes

🏠 Real-world analogy: The stack is like a stack of plates — you add and remove from the top (LIFO). The heap is like a warehouse — items are placed wherever there's space, and a cleaner (GC) periodically removes items nobody needs anymore.

💻 Code Example

codeTap to expand ⛶
1// CALL STACK VISUALIZATION
2function third() {
3 console.log("third"); // Stack: [main, first, second, third]
4 // When third() returns, its frame is popped
5}
6function second() {
7 third(); // Stack: [main, first, second] → pushes third
8 console.log("second");
9}
10function first() {
11 second(); // Stack: [main, first] → pushes second
12 console.log("first");
13}
14first(); // Stack: [main] → pushes first
15
16// STACK OVERFLOW — exceeding call stack limit
17function infiniteRecursion() {
18 return infiniteRecursion(); // Never stops!
19}
20// infiniteRecursion(); // RangeError: Maximum call stack size exceeded
21
22// STACK VS HEAP — primitives vs objects
23let a = 10; // Stack: a = 10
24let b = a; // Stack: b = 10 (COPY of value)
25b = 20; // a is still 10! Primitives are copied
26
27let obj1 = { x: 1 }; // Stack: obj1 → ref to Heap object
28let obj2 = obj1; // Stack: obj2 → SAME ref in Heap
29obj2.x = 99; // obj1.x is also 99! Objects share references
30
31// MEMORY LEAK EXAMPLE — closure holding reference
32function createLeak() {
33 const hugeData = new Array(1000000).fill("data");
34 return function() {
35 // This closure keeps 'hugeData' alive forever!
36 console.log(hugeData.length);
37 };
38}
39const leakyFn = createLeak(); // hugeData can never be GC'd
40
41// PREVENTING MEMORY LEAKS
42function noLeak() {
43 const hugeData = new Array(1000000).fill("data");
44 const length = hugeData.length; // Extract what you need
45 return function() {
46 console.log(length); // Only keeps the number, not the array
47 };
48}
49
50// TypeScript — understanding value vs reference types
51function demonstrateMemory(): void {
52 // Primitives (stack) — passed by value
53 let x: number = 5;
54 let y: number = x;
55 y = 10;
56 console.log(x); // 5 — unchanged
57
58 // Objects (heap) — passed by reference
59 const arr1: number[] = [1, 2, 3];
60 const arr2: number[] = arr1; // Same reference!
61 arr2.push(4);
62 console.log(arr1); // [1, 2, 3, 4] — both changed!
63
64 // Deep copy to avoid shared references
65 const arr3: number[] = [...arr1]; // New array in heap
66 arr3.push(5);
67 console.log(arr1); // [1, 2, 3, 4] — unchanged
68}

🏋️ Practice Exercise

Practice Problems:

  1. Draw the call stack for: first() calling second() calling third()
  2. What happens in memory when you do let a = {x:1}; let b = a; b.x = 2;?
  3. Write code that causes a stack overflow and catch the error
  4. Explain why recursive fibonacci uses O(n) space even though it makes O(2ⁿ) calls
  5. Identify the memory leak in a given code snippet with closures
  6. What's the maximum call stack depth in your browser? Write code to measure it
  7. Explain the difference between shallow copy and deep copy in terms of heap memory
  8. Why are strings sometimes said to be "on the stack" even though they're objects?
  9. How does JavaScript's garbage collector know when to free memory?
  10. Write a function that demonstrates pass-by-value vs pass-by-reference

⚠️ Common Mistakes

  • Thinking objects are stored on the stack — objects are ALWAYS on the heap; only the reference (pointer) is on the stack

  • Not understanding that assigning an object to a new variable copies the REFERENCE, not the object — both point to the same heap memory

  • Thinking JavaScript has manual memory management — it uses automatic garbage collection (mark-and-sweep)

  • Forgetting that closures keep outer scope variables alive — this can cause memory leaks if large objects are captured

  • Confusing the call stack with the event loop — the call stack handles synchronous execution; the event loop handles async callbacks

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for Call Stack & Memory Model (Stack vs Heap)