The call stack is how a program remembers where it is and where to go back to. Each function call pushes a frame (its arguments, local variables and return address) on top. When the function returns, its frame is popped and execution continues in the frame below. Last in, first out.

How it works

function b() {
  console.log('in b');            // stack: b, a
}
function a() {
  console.log('in a');            // stack: a
  b();                            // push b
  console.log('back in a');       // b was popped; a continues where it left off
}
a();
// in a
// in b
// back in a

Each thread has its own call stack. In JavaScript there is one main thread, so there is one call stack, and the event loop only runs a queued callback when that stack is empty.

Stack overflow

The stack has a fixed size. Recursion that never stops (or goes too deep) fills it:

  • JavaScript throws RangeError: Maximum call stack size exceeded.
  • Python raises RecursionError; the default limit is 1000 frames (sys.getrecursionlimit()).
import sys
def depth(n):
    try:
        return depth(n + 1)
    except RecursionError:
        return n
print(depth(0) < sys.getrecursionlimit())   # True

Fixes: make sure every recursion has a base case, or rewrite deep recursion as a loop with your own stack (a list).

The heap is where objects live; frames on the stack only hold references to them.

Check yourself

Leads to

  • #1 · The event loop: How JavaScript does many things at once with one thread, and why promise callbacks run before timers.
  • Event loop: The rule that decides when queued callbacks run in JavaScript - only when the call stack is empty, microtasks first.
  • Thread: A sequence of instructions the operating system can schedule. Threads in one process share memory; each has its own call stack.