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.
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.
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:
first starts
second runs
first endsThe 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
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:
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:
3
2
1Each 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.
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:
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:1The 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.
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:
A
C
BEven 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
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.
Frequently Asked Questions
Is the call stack the same as the execution stack?
How many frames can the call stack hold?
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.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.