cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

The call stack

Learn how JavaScript tracks function calls by pushing and popping stack frames, how to read stack traces, why stack overflow happens, and how DevTools shows the stack while debugging.

By the end, you can
  • 01
    Trace callsExplain which stack frame is on top and why callers wait underneath.
  • 02
    Read failuresUse a stack trace to find where an error started and how code got there.
  • 03
    Debug deliberatelyRecognize overflow, inspect Call Stack in DevTools, and name functions for better traces.

The stack of calls

A JavaScript program is always running somewhere: at the top level of a script, inside a function, or inside a function that was called by another function. The call stack is the engine’s record of those active calls.

The previous lesson, Execution contexts, introduced the context a function call needs: its local variables, parameters, scope links, and where to continue. A stack frame is the stack’s entry for one active call. It is the note that says “this execution context is currently running, and here is where it returns.”

Real-life analogySticky notes on your desk

Imagine each function call writes a sticky note: where it was, what local names it has, and where to go back. The newest note goes on top. When that function returns, the note is torn off, and the note underneath continues.

In real life: Write a note for the task you just started
In JavaScript: Push a new stack frame for a function call
In real life: The top note is what you are doing now
In JavaScript: The top frame is the currently running function
In real life: Tear off the top note when that task is done
In JavaScript: Pop a frame when a function returns
In real life: Photocopy the pile when something goes wrong
In JavaScript: A stack trace captures active frames

Where the analogy stops: Real notes can be rearranged. The call stack is stricter: JavaScript can only add to the top or remove from the top.

This lesson goes deeper than the Recursion lesson’s plates analogy: you will watch ordinary helper functions push and pop frames, read a stack trace from top to bottom, safely trigger an overflow, and use the DevTools Call Stack panel with purpose.

Pushing & popping frames

STEP THROUGH

A frame is pushed whenever JavaScript calls a function. The caller pauses underneath. When the callee returns, its frame is popped, and the caller continues from the waiting line.

The checkout example has a realistic shape: checkout calls calculateTotal, then applyCoupon, then formatReceipt. The helper roundPrice is called twice from different callers, proving that “same function” does not mean “same frame.”

Run checkout and watch frames
Step 0 of 22Ready
Your turn: follow the blue line

Watch frames push when a function is called and pop when it returns. Change the coupon and the middle path changes.

Running in
  1. script
Next: line 22
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
function checkout(cart, coupon) {  const subtotal = calculateTotal(cart);  const discounted = applyCoupon(subtotal, coupon);  const receipt = formatReceipt(discounted);  return receipt;}function calculateTotal(cart) {  const total = cart.items.reduce((sum, item) => sum + item.price, 0);  return roundPrice(total);}function applyCoupon(total, coupon) {  if (coupon === "SAVE10") {    return roundPrice(total * 0.9);  }  return roundPrice(total);}function formatReceipt(total) {  return "$" + total.toFixed(2);}function roundPrice(value) {  return Math.round(value * 100) / 100;checkout({ items: [{ price: 19.99 }, { price: 5 }] }, "SAVE10");
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the coupon passed on the last line.

Changing the coupon starts a fresh recorded run.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
A frame holds a context

A stack frame is the card for one active function call. It points at that call’s execution context: parameters, local variables, scope links, and the return location. Later lessons, Stack frames & calling conventions and Stack limits & stack overflow, zoom in on what engines store internally.

Visualize the stack growing and shrinking

INTERACTIVE

The player above shows source lines and values. This playground shows the same run as stacked cards. It is a conceptual display of recorded events, not a secret window into the engine.

Conceptual stack visualizer
Recorded checkout codePop out in the code editor (opens in a new tab)JavaScript
function checkout(cart, coupon) {  const subtotal = calculateTotal(cart);  const discounted = applyCoupon(subtotal, coupon);  const receipt = formatReceipt(discounted);  return receipt;}function calculateTotal(cart) {  const total = cart.items.reduce((sum, item) => sum + item.price, 0);  return roundPrice(total);}function applyCoupon(total, coupon) {  if (coupon === "SAVE10") {    return roundPrice(total * 0.9);  }  return roundPrice(total);}function formatReceipt(total) {  return "$" + total.toFixed(2);}function roundPrice(value) {  return Math.round(value * 100) / 100;}checkout({ items: [{ price: 19.99 }, { price: 5 }] }, "SAVE10");
Top of stack1 frame(s)

checkout

couponSAVE10
Step 1 of 12push

Push: checkout. A call creates the first function frame.

Conceptual display of recorded push/pop events. It is not reading your engine's private memory.
Real-life analogyA cafeteria tray stack

A tray stack is last-in, first-out. You cannot remove the tray in the middle without taking the ones above it. The call stack follows that same rule.

In real life: Put a tray on top
In JavaScript: Call a function and push a frame
In real life: Take the top tray first
In JavaScript: Return from the newest call first
In real life: The bottom tray waits
In JavaScript: Older callers wait underneath

Where the analogy stops: A cafeteria stack only stores identical trays. Real stack frames can hold different parameters and locals for each call.

Reading stack traces

REAL STACK

A stack trace is a snapshot of the call stack at the moment an error was created or thrown. Formats differ between V8, SpiderMonkey, JavaScriptCore, browsers, and Node.js, but the practical reading habit is the same: start at the top to find where the error began, then read downward to learn who called whom.

Capture a real stack in this browser
Throw from the deepest callPop out in the code editor (opens in a new tab)JavaScript
function checkout() {  calculateTotal();}function calculateTotal() {  applyCoupon();}function applyCoupon() {  throw new Error("Coupon database is offline");}checkout();
Real outputclient-only
Click Run to capture your browser's stack trace.

Not measured yet.

Try it yourself

Engines format stacks differently, so this experiment captures the real error.stack string on click, after hydration.

Depth varies by engine, frame size, extensions, and settings. Treat the number as approximate.
How to read a V8-style stack trace
PartMeaningHow to use it
Error: Coupon database is offlineThe error name and messageUnderstand what failed before hunting where.
at applyCoupon (checkout.js:7:9)Top frame: newest call, line 7, column 9Usually the place the error started.
at calculateTotal ...The caller of applyCouponRead downward to reconstruct the path.
at checkout.js:9:1Top-level script locationThe bottom frame is the oldest shown caller.
Verified V8-style exampletext
Error: Coupon database is offline    at applyCoupon (checkout.js:7:9)    at calculateTotal (checkout.js:4:3)    at checkout (checkout.js:2:3)    at checkout.js:9:1

Anonymous functions make traces harder to read because the engine has less name information. A named callback such as function showItem(item) { ... } gives the trace a useful label. V8 also exposes non-standard controls such as Error.captureStackTrace and Error.stackTraceLimit; the later Stack traces & the stack trace API lesson goes into those carefully.

Stack overflow

MEASURE

The stack is finite. If calls keep piling up without returning, the pile hits the ceiling. Most browsers throw RangeError: Maximum call stack size exceeded; Firefox may report InternalError: too much recursion. The exact depth varies by engine, frame size, optimization, and settings.

Real-life analogyA sticky-note pile that hits the ceiling

A healthy pile grows and shrinks. An accidental infinite recursion only grows, so eventually there is no room for another note.

In real life: Every note adds height
In JavaScript: Every recursive call pushes a frame
In real life: No note is removed
In JavaScript: No base case returns, so nothing pops
In real life: The pile reaches the ceiling
In JavaScript: The engine throws a stack overflow error

Where the analogy stops: The engine’s limit is not a visible ceiling and it is not standardized. It changes between browsers and even between functions.

A setter that calls itself foreverPop out in the code editor (opens in a new tab)JavaScript
const user = {  set name(value) {    this.name = value;  }};try {  user.name = "Ada";} catch (error) {  console.log(error.name);}

The setter assigns to this.name from inside the setter for name, so the setter calls itself again and again. Store the backing value somewhere else, such as this._name or a private field, to break the loop.

Fun fact

Stack overflow is not only a recursion problem. Any cycle of synchronous function calls can do it: a setter that assigns to itself, a getter that reads itself, or two functions that call each other forever.

The stack in DevTools

TRY IT

In the Debugging in the browser lesson, you used breakpoints and stepped into and out of functions. When code is paused, the DevTools Call Stack panel is the live version of the same idea. The top entry is the current function. Older callers are below.

Paste this into DevTools and pausePop out in the code editor (opens in a new tab)JavaScript
function checkout() {  calculateTotal();}function calculateTotal() {  applyCoupon();}function applyCoupon() {  debugger;}checkout();
  1. Open DevTools, choose Sources or Debugger, and run the snippet.
  2. When debugger; pauses, read the Call Stack panel.
  3. Click applyCoupon, calculateTotal, and checkout.
  4. Watch the Scope or Variables panel change to that frame’s locals.

Modern DevTools can also show async stack traces for callbacks and promises. Those are reconstructed histories, not one single synchronous stack. You will meet them again in Async stack traces.

Push or pop?
  • A function call starts running
  • A function reaches return
  • A function throws and nobody catches inside it
  • forEach calls your callback
  • const total = 3 + 4 runs
  • Clicking an older frame in DevTools
Try it yourself
0 of 6 correct

Sort each event by what it does to the call stack.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you’ll use this

You use the call stack whenever you debug code that jumps between helpers. If an error appears inside roundPrice, the stack tells you whether the bad input came from calculateTotal or applyCoupon. If a callback fails inside forEach, the stack tells you which application function started the iteration.

Similar tools, different questions
ToolQuestion it answersTypical use
Call stackWhere am I, and who called me?Step through nested functions.
Stack traceWhere did this error start?Read logs and bug reports.
Console logsWhat value did this line produce?Quick checks while developing.
BreakpointsWhat does the program know right now?Pause and inspect locals.

Common misconceptions

“The stack stores every variable forever.”

Frames only represent active calls. Returned function frames are gone, although closures can keep referenced environments alive.

“The stack trace reads from oldest to newest.”

Most JavaScript traces show the newest frame first. Read downward to follow callers backward.

“Throwing leaves frames active.”

Throwing unwinds the stack: frames pop until a matching catch handles the error or the program reports it.

“A named function can only have one frame.”

Each call creates a separate frame. Recursion can show the same name many times.

“DevTools Call Stack is time travel.”

Clicking older frames changes what you inspect. It does not rerun or rewind the program.

Practice: read the stack

5 EXERCISES
Exercise 1 · Warm-upPredict the logs

Predict the console output in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function outer() {
  console.log("outer start");
  inner();
  console.log("outer end");
}
function inner() {
  console.log("inner");
}
outer();

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFind the top frame

    Read the stack trace and name the function where the error started.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    Error: Coupon database is offline
        at applyCoupon (checkout.js:7:9)
        at calculateTotal (checkout.js:4:3)
        at checkout (checkout.js:2:3)
        at checkout.js:9:1

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix the setter recursion

      What property should the setter assign to instead of this.name?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const user = {
        set name(value) {
          this.name = value;
        }
      };
      try {
        user.name = "Ada";
      } catch (error) {
        console.log(error.name);
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeFind your browser’s depth limit

        Click the measurement button above and record the approximate depth.

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeName a callback for better traces

          What callback name should appear in the stack trace?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function reportFailure() {
            ["bad"].forEach(function showItem(item) {
              throw new Error("Cannot read " + item);
            });
          }
          try {
            reportFailure();
          } catch (error) {
            console.log(error.stack.includes("showItem"));
          }

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What happens when checkout() calls calculateTotal()?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this program print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function outer() {
                console.log("outer start");
                inner();
                console.log("outer end");
              }
              function inner() {
                console.log("inner");
              }
              outer();

              Choose an answer to see the explanation.

            3. Question 3 of 7In a V8-style stack trace, which frame usually appears first after the error message?

              Choose an answer to see the explanation.

            4. Question 4 of 7Why can the same function name appear twice in a stack?

              Choose an answer to see the explanation.

            5. Question 5 of 7What error type does the accidental setter usually throw in V8/JavaScriptCore when it recurses until the stack limit?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does clicking a frame in the DevTools Call Stack panel do?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does the named-callback trace check print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function reportFailure() {
                ["bad"].forEach(function showItem(item) {
                  throw new Error("Cannot read " + item);
                });
              }
              try {
                reportFailure();
              } catch (error) {
                console.log(error.stack.includes("showItem"));
              }

              Choose an answer to see the explanation.

            Key takeaways

            • Calling a function pushes a stack frame; returning pops it.
            • The top frame is the currently running function. Callers wait underneath.
            • A stack trace snapshots active calls when an error is created or thrown.
            • Stack overflow happens when calls keep nesting until the stack limit is reached.
            • DevTools lets you inspect each paused frame’s source and local variables.

            One-liner.
            The call stack is JavaScript’s last-in, first-out list of active function calls.

            Up next: Hoisting & the temporal dead zone.

            CompleteFrontend Clear concepts. Working examples.