Manifest Stack home

How Call Stack Works

Part 1: Step-by-Step Trace

The best way to understand the call stack is to trace through code manually. Let's watch each push and pop happen.

Example 1 — Two function calls in sequence

javascript
function multiply(a, b) {
    return a * b;
}

function square(n) {
    return multiply(n, n);
}

let result = square(4);
console.log(result); // 16

// Step-by-step call stack:
//
// → square(4) called
//   Stack: [ square ]
//
// → multiply(4, 4) called inside square
//   Stack: [ square | multiply ]   ← multiply is on top
//
// → multiply returns 16
//   Stack: [ square ]              ← multiply popped
//
// → square returns 16
//   Stack: []                      ← square popped
//
// → console.log(16) called
//   Stack: [ console.log ]  → runs → popped
//   Stack: []

Example 2 — Function with multiple calls

javascript
function printName() {
    let first = getFirst();
    let last  = getLast();
    console.log(first + " " + last);
}

function getFirst() { return "Arjun"; }
function getLast()  { return "Kumar"; }

printName();

// Stack trace:
// printName pushed
//   → getFirst pushed → returns "Arjun" → getFirst popped
//   → getLast pushed  → returns "Kumar" → getLast popped
//   → console.log pushed → logs "Arjun Kumar" → popped
// printName popped
// Stack: []

Part 2: The Call Stack and Recursion

Recursion stacks up frames quickly because each recursive call adds a new frame before any of them have returned. The stack only unwinds after hitting the base case.

Factorial — recursive stack build-up

javascript
function factorial(n) {
    if (n <= 1) return 1;        // base case
    return n * factorial(n - 1);
}

factorial(4);

// Stack builds up:
// [ factorial(4) ]
// [ factorial(4) | factorial(3) ]
// [ factorial(4) | factorial(3) | factorial(2) ]
// [ factorial(4) | factorial(3) | factorial(2) | factorial(1) ]
//                                                ↑ hits base case!
// Stack unwinds:
// factorial(1) returns 1   → popped
// factorial(2) returns 2*1 → popped
// factorial(3) returns 3*2 → popped
// factorial(4) returns 4*6 → popped → result: 24

Part 3: Synchronous — Stack Must Be Empty for Async

This is the most important thing to understand: async callbacks only run when the call stack is completely empty.The event loop will not push a callback onto the stack while something else is running.

setTimeout with 0ms — still async

javascript
function first() {
    console.log("1");
    setTimeout(() => console.log("2"), 0); // 0ms delay!
    console.log("3");
}

first();

// Output: 1  3  2
//
// Why? setTimeout sends callback to Web API even with 0ms.
// The callback goes to the queue.
// The event loop only picks it up AFTER the stack is empty.
//
// Stack trace:
// first() pushed
//   → logs "1"
//   → setTimeout registers callback (Web API)
//   → logs "3"
// first() popped → Stack is empty!
// Event loop: picks up setTimeout callback
//   → logs "2"

Long-running sync code blocks everything

javascript
// ❌ This blocks the entire browser for ~3 seconds
function blockFor3Seconds() {
    const start = Date.now();
    while (Date.now() - start < 3000) {} // busy-waiting
    console.log("Done blocking");
}

setTimeout(() => console.log("Timer fired"), 500);
blockFor3Seconds();

// Output:
// Done blocking   (after 3s)
// Timer fired     (after 3s, even though it was set for 0.5s!)
//
// The timer callback was stuck in the queue
// because the stack was never empty until blockFor3Seconds returned.