The JavaScript engine
The call stack
Track nested function calls and understand why the most recently called function returns first.
Every time a function is called, the engine creates a stack frame for it. The frame holds the function’s local variables and the place where execution should continue when the function returns. Frames pile up in the call stack.
Let’s follow a small example:
function formatName(name) {
return name.trim().toUpperCase()
}
function renderUser(user) {
return `<p>${formatName(user.name)}</p>`
}
renderUser({ name: ' Ada ' })
The stack grows in this order:
- the top-level script
renderUser()formatName()
Then it shrinks in reverse. formatName() returns 'ADA' and its frame disappears. renderUser() picks up where it left off, builds the string, returns, and its frame disappears too. The last function called is the first one removed. This is why we say the stack is last-in, first-out.
Why this matters for debugging
This model explains two tools you use every day.
An error stack trace is a snapshot of the call stack at the moment the error was thrown. Read it bottom to top and you see the exact chain of calls that led there.
The Call Stack pane in the debugger is the same thing, live. When you pause on a breakpoint, you can click any earlier frame and inspect the values that caller had at the time. This is how you find out who passed the wrong argument, not just where it blew up.
Stack overflow
The stack has a limit. Recursion must reach a base case:
function countdown(value) {
if (value === 0) return
countdown(value - 1)
}
Remove the if line and frames keep piling up until the engine refuses to add another one. In Chrome you get:
Uncaught RangeError: Maximum call stack size exceeded
When you see this error, look for a recursive function without an exit, or two functions that call each other forever.
Step through it in the debugger
Put the first snippet in a page, open the Sources panel, and set a breakpoint on the return line inside formatName(). Reload.
Execution pauses, and the Call Stack pane shows formatName on top, renderUser below it, and the anonymous top-level script at the bottom. Click renderUser and the Scope pane shows you the user object it received. Then use Step out once and watch the formatName frame vanish while renderUser becomes the active one again.
Lesson completed