JavaScript runs one thing at a time. When a function calls another, it has to remember where it came from so it can go back once the work is done.
That return notebook has a name: the call stack. Knowing it changes the way you read an error message, and explains why an unbounded recursion brings the whole program down.
Definition
The call stack is the structure in which the engine stacks the functions currently running. Every call pushes an entry on top, every return pops it off. The last one in is always the first one out.
function third() {
throw new Error("something broke");
}
function second() { third(); }
function first() { second(); }
try {
first();
} catch (error) {
console.log(error.stack.split("\n").slice(0, 4).join("\n"));
}
// Error: something broke
// at third
// at second
// at firstThe trace reads top to bottom like a path in reverse: the first line is the exact spot of the incident, the following ones say who asked for what to get there.
What the stack does not hold
This is the part that surprises people most. A setTimeout() or a Promise does not sit on the stack while it waits: its callback is set aside and only comes back once the stack has been fully emptied.
console.log("1 start");
setTimeout(() => console.log("3 timer"), 0);
console.log("2 end");
// 1 start
// 2 end
// 3 timerEven with a zero delay, the timer comes last. That is the job of the Event loop, which only refills the stack once it is empty. Direct consequence: a function that blocks the stack freezes everything else, rendering included.
Stack overflow
The stack has a size limit. Going past it produces an immediate error, almost always caused by a Recursion with no stopping condition.
let depth = 0;
function goDeeper() {
depth += 1;
goDeeper();
}
try {
goDeeper();
} catch (error) {
console.log(error.constructor.name); // RangeError
console.log(depth > 1000); // true
}An overflow does not always mean infinite recursion. A very deep tree, or two functions calling each other, produce the same error with perfectly correct logic.
Frequently asked questions
What is the maximum stack size?
No specification fixes it, and it varies with the engine, the browser and the available memory. The order of magnitude sits around ten thousand nested calls. Code that gets close has a design problem anyway: depth should depend on the data, never on luck.
Why does my error trace only show unreadable names?
Because the deployed code is minified: function names were shortened to reduce file size. The fix is to publish a Source map alongside the compiled version, which the monitoring tool uses to recover the original names and line numbers.
Does an async function occupy the stack while it waits?
No. At the await point, the function is removed from the stack and its state is set aside. It returns when the result arrives, which frees the engine for everything else in the meantime. That is exactly what keeps a page responsive during a network call.