How the JS Call Stack Handles Function Execution

The call stack tracks which function is currently running using LIFO order. Learn how frames are pushed, popped, and how stack traces help debugging.

7 min read

The call stack is a data structure the JavaScript engine uses to track function calls. It works like a stack of plates: the last one placed on top is the first one removed.

When a function is called, the engine pushes a frame onto the stack. When the function returns, the engine pops that frame off.

At any moment, the function whose frame is on top of the stack is the one currently executing. Every function underneath is paused, waiting for the top frame to finish.

The LIFO Rule

LIFO stands for Last In, First Out. It is the only rule the call stack follows. To see it in action, consider two functions where one calls the other.

javascriptjavascript
function first() {
  console.log("first starts");
  second();
  console.log("first ends");
}
 
function second() {
  console.log("second runs");
}
 
first();

When this code runs, the call stack goes through distinct states. At the start, only the global context is on the stack.

Then first is called and pushed. Inside first, second is called and pushed.

When second finishes, it is popped. Then first finishes and is popped.

The output confirms this order:

texttext
first starts
second runs
first ends

The engine never runs first and second at the same time. It runs first, pauses it to run second, then resumes first. This is single-threaded, synchronous execution.

What Is Inside a Stack Frame

Each frame on the call stack is not just a function name. It is an execution context containing the function's local variables and parameters, a return address telling the engine where to resume, the value of the this keyword for that call, and a reference to the outer scope.

When a frame is popped, all its local variables are destroyed. That is why a variable declared inside a function is not accessible after the function returns.

Visualizing the Push and Pop

Call stack push and pop during nested function calls

The stack grows when functions call other functions and shrinks when they return. The engine only ever looks at the top frame. Everything below is suspended.

What Happens When the Stack Overflows

The call stack has a limit. The exact limit varies by engine, but it is typically between 10,000 and 15,000 frames in V8. When the limit is hit, the engine throws a RangeError.

The most common cause is unbounded recursion: a function that calls itself with no base case. Each call pushes a new frame, nothing ever returns, and the stack grows until it crashes.

Here is a correct recursive function with a base case:

javascriptjavascript
function countdown(n) {
  if (n <= 0) return;
  console.log(n);
  countdown(n - 1);
}
 
countdown(3);

Running countdown(3) prints each number on its own line. The recursion pushes frames for 3, 2, and 1, then the base case fires and the stack unwinds:

texttext
3
2
1

Each call pushes a frame, but when n reaches 0, the function returns instead of calling itself again. The frames unwind one by one, and the output shows the countdown in order. For more on recursion patterns, see /javascript/how-to-use-recursion-in-javascript-full-tutorial.

How the Call Stack Produces Stack Traces

When an error is thrown, the engine captures the current state of the call stack as a stack trace. The trace shows which functions were active and the line numbers where each call happened.

javascriptjavascript
function fetchData() {
  throw new Error("Network failure");
}
 
function loadPage() {
  fetchData();
}
 
function init() {
  loadPage();
}
 
init();

The resulting error output would show fetchData at the top where the error occurred. Below it, loadPage would appear, then init, then the global call site. This trace shows the full path:

texttext
Error: Network failure
    at fetchData (app.js:2:9)
    at loadPage (app.js:6:3)
    at init (app.js:10:3)
    at app.js:13:1

The stack trace reads from bottom to top: init called loadPage, which called fetchData, where the error was thrown. This is why stack traces are your best debugging tool. You can trace the exact path your code took.

Learn how to read stack traces effectively in /javascript/how-to-read-and-understand-javascript-stack-traces.

The Call Stack Is Synchronous Only

This is the most important thing to understand about the call stack: it only handles synchronous code. When you use setTimeout or a Promise, the callback does not go directly onto the stack.

javascriptjavascript
console.log("A");
 
setTimeout(() => {
  console.log("B");
}, 0);
 
console.log("C");

Running this code produces a clear result that demonstrates how asynchronous callbacks wait for the stack to empty before they can run:

texttext
A
C
B

Even with a delay of 0 milliseconds, B prints last. The first two console.log calls run on the stack and print A and C. The setTimeout callback goes through the browser's timer API, enters the task queue, and only reaches the stack after the synchronous code finishes.

The call stack must be completely empty before any async callback can run. This is why a long-running synchronous loop freezes the entire page: no async callbacks, not even click handlers, can get onto the stack while the loop is running. For a full comparison of how the call stack interacts with async queues, see /javascript/call-stack-vs-task-queue-vs-microtask-queue-js.

How to Inspect the Call Stack in DevTools

Every modern browser lets you see the call stack live while debugging. Open DevTools, go to the Sources tab, set a breakpoint by clicking a line number, and trigger the function.

When the breakpoint hits, the Call Stack panel on the right shows every active frame. You can click any frame to jump to that function's code and inspect its local variables.

Common Call Stack Mistakes

Blocking the stack. A synchronous operation that takes too long prevents everything else from running, including UI updates and event handlers. For CPU-heavy work, consider breaking it into smaller chunks with setTimeout or moving it to a Web Worker.

Confusing the stack with async queues. The call stack does not hold setTimeout callbacks or Promise handlers. Those wait in separate queues and only enter the stack when it is empty and all microtasks are drained.

Missing return statements in recursion. A function that does not explicitly return still returns undefined and its frame is still popped. But forgetting to return early in a recursive function causes stack overflow because the function keeps calling itself with no exit condition.

Rune AI

Rune AI

Key Insights

  • The call stack is a LIFO structure that tracks active function calls.
  • A new frame is pushed when a function is called and popped when it returns.
  • The currently executing function is always the frame on top of the stack.
  • Stack overflow happens when too many frames build up without returning.
  • The call stack only handles synchronous code. Async callbacks wait in queues.
RunePowered by Rune AI

Frequently Asked Questions

Is the call stack the same as the execution stack?

Yes. The terms call stack and execution stack refer to the same thing: the LIFO data structure that tracks active function calls in JavaScript.

How many frames can the call stack hold?

There is no fixed number. It depends on available memory and the engine. Chrome V8 typically allows around 10,000 to 15,000 frames before throwing a stack overflow error.

Conclusion

The call stack is the mechanism that keeps track of where your program is. Every function call pushes a frame. Every return pops it. The stack is simple LIFO logic, but understanding it makes debugging stack traces, avoiding stack overflow, and reasoning about async behavior much easier.