learn
JavaScript

Call stack and execution context

Frames, push and pop, overflow, and reading a trace.

Every call pushes a frame — its variables plus where to resume — and every return pops it. Interviewers ask this because "Maximum call stack size exceeded" and frozen pages both start here, and reading a trace starts here too.

Real-life situation

A receipt printer recurses into bundled items, but a bundle containing itself loops forever and the tab dies with a stack overflow. The trace shows the same function repeated — the giveaway. The fix is a visited set plus an iterative fallback, but noticing the repeated frame is the interview skill.

Visual

Frames pile upbrowser

Call stack

login

Frames used

1 of ~10,000

Limit

empty

Console

—

login() is called. The login button calls login(). A frame for login goes on the stack.

Step 1 of 5
scenario.js
function sendOtp() {
  return sendOtp(); // oops: calls itself, no exit
}
function login() {
  sendOtp();
}
login();

Try it

Push and pop order
javascriptTab moves to the next control
function factorial(n) {
console.log("enter " + n);
if (n <= 1) {
  console.log("base");
  return 1;
}
const rest = factorial(n - 1);
console.log("leave " + n);
return n * rest;
}
console.log("result " + factorial(3, 0));
function start() {
return middle();
}
function middle() {
return end();
}
function end() {
return "done";
}
console.log(start());
Press Run to see the output.
Expected output
enter 3
enter 2
enter 1
base
leave 2
leave 3
result 6
done
Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. Console code runs in a worker with a 5-second limit.

Try raising the factorial input until the runner stops you, and read which error names the stack. Then trace the leave lines back to their frames.

When depth matters

Recursion fits nested data (trees, comments, DOM walks). Flat sequences want loops, and unbounded depth wants chunking so frames pop between batches. Depth is always bounded — engines don't do tail-call optimization.

Frame order — guess first, then reveal.

predict.js
function a() {
console.log("a-start");
b();
console.log("a-end");
}
function b() {
console.log("b");
}
a();

Interview questions

What is the call stack?basic

The to-do pile of running functions: every call pushes a frame (its variables plus where to resume), every return pops it. It is why errors print a "stack" of who-called-whom and why deep recursion crashes — "Maximum call stack size exceeded" means the pile outgrew its fixed memory. One thread, one stack, last-in-first-out: the whole mental model in a sentence.

Interviewers also ask

  • What exactly is stored in a frame?
What is a stack overflow in JavaScript?basic

Calls piling faster than returns clear them, until the fixed-size stack runs out — almost always runaway recursion with no (or unreachable) base case. The engine throws RangeError and unwinds everything. Fixes: find the missing base case, convert to iteration, or for legitimately deep work, process in chunks so frames can pop between batches.

Interviewers also ask

  • How do you convert recursion to iteration?
What is an execution context, in plain terms?intermediate

The engine's per-call workspace: the function's variables, its scope link, its this, plus a bookmark for where to resume the caller. Creation (set up the workspace, hoist declarations) runs before execution (run the lines). Each stack frame IS an execution context in flight — two names for the box your function runs inside.

Interviewers also ask

  • What happens during the creation phase?
Why does a long-running function freeze the page?intermediate

The single thread can only do one thing: while your function occupies the stack, no rendering, no events, no timers fire — the page just sits. Anything over ~50ms risks visible jank. Fixes follow one idea: get off the stack — chunk the work with setTimeout, yield with await, or move it to a worker. "Don't block" is the whole browser performance story in three words.

Interviewers also ask

  • How long is too long for one task?
How do you read a stack trace?basic

Top frame threw; read downward for who called whom, file by file, newest first. Your code is usually a few frames down under library internals — scan for the first familiar filename. The line numbers point at the call sites, and Caused by chains (or AggregateError errors arrays) mean keep reading past the first trace. Practice on one real trace beats memorizing the format.

Interviewers also ask

  • Which frame do you look at first?
Is recursion ever the right tool in JavaScript?intermediate

Yes for naturally nested data — trees, DOM walks, nested comments — where recursion mirrors the shape and stays shallow. No for long flat sequences (use loops) or unbounded depth (chunk it). Tail-call optimization would save deep recursion, but engines don't implement it, so depth is always bounded by the stack — say that sentence and the interviewer knows you get it.

Interviewers also ask

  • Why can't you rely on tail-call optimization?

Key takeaways

  • Calls push frames; returns pop them; last in, first out.
  • Overflow means calls outrun returns — find the missing base case.
  • Read traces top-down: the thrower first, your code a few frames down.

References

Last verified: 2026-10-12.

On this page