completefrontend
Open lab My progress
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
    Follow callsSay which frame is on top of the call stack, and why the functions that called it wait underneath.
  • 02
    Read a stack traceUse a stack trace to find where an error started and which calls led there.
  • 03
    Debug with the stackSpot a stack overflow, use the Call Stack panel in DevTools, and name your functions so traces are easier to read.

The stack of calls

JavaScript runs one piece of code at a time. That code might be at the top level of a script, which means it is not inside any function. It might be inside a function. Or it might be inside a function that another function called.

The call stack is the list JavaScript keeps of the function calls that are running right now. A stack is a pile where you add things on top and take them off the top.

Each entry in the call stack is called a stack frame, or just a frame. One frame stands for one call. In the last lesson, Execution contexts, you saw what a call needs to run: its parameters, its local variables, and links to outer scopes. The frame holds on to that context. It also remembers where to go back to when the call returns.

One call makes one framePop out in the code editor (opens in a new tab)JavaScript
function greet(user) {
  console.log("Hello " + user);
}
greet("Asha");

Line 1 creates greet. It doesn't run yet. Line 4 calls it with "Asha". So JavaScript puts a frame for greet on top of the stack. Putting a frame on top is called a push.

greet prints Hello Asha and returns. Then its frame comes off the top. Taking a frame off is called a pop. After that, the script goes on.

Left: line 4 calls greet. A greet card goes on top of the script card. This is a push. Right: greet returns. The greet card is taken off, and only the script card is left. This is a pop.line 4 calls greetgreet("Asha")scriptpushgreet returnsscriptpop
When line 4 calls greet("Asha"), a new frame for that call goes on top of the pile. This is a push. When greet returns, its frame comes off the top. This is a pop. The script frame at the bottom then goes on.
Real-life analogySticky notes on your desk

Each time you start a task, you write a sticky note. The note says what you are doing and where to go back to. You stick the newest note on top of the pile. When you finish that task, you tear off the top note. Then you go back to the note underneath.

In real life: Stick a new note on top when you start a task
In JavaScript: A call pushes a new frame on top
In real life: The top note is the task you are doing now
In JavaScript: The top frame is the function running now
In real life: Tear off the top note when that task is done
In JavaScript: A return pops the top frame off
In real life: Photocopy the pile when something goes wrong
In JavaScript: A stack trace saves the list of frames when an error happens

The Recursion lesson used a pile of plates to show a function that calls itself. This lesson uses ordinary functions. You will:

  • watch helper functions push and pop frames,
  • read a stack trace from top to bottom,
  • cause a stack overflow on purpose, safely,
  • and use the Call Stack panel in DevTools.

Adding and removing frames

STEP THROUGH

Each time JavaScript calls a function, it pushes a frame. The function that made the call is called the caller. The caller stops and waits underneath. When the called function returns, its frame is popped. Then the caller goes on from the line where it stopped.

The checkout example below uses a few small helper functions:

  • checkout calls calculateTotal, then applyCoupon, then formatReceipt.
  • calculateTotal and applyCoupon both call roundPrice.

So roundPrice runs twice, for two different callers. Each of those calls gets its own frame. One function can have many frames: one for each call.

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

A call puts a frame on top. A return takes it off. Change the coupon to see the middle part take a different line.

Running in
  1. script
Next: line 23
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;}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the coupon passed on the last line.

Pick a coupon to start the run again from the first step.

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.
The call stack while roundPrice runs. From top to bottom: roundPrice is running now. calculateTotal waits for roundPrice. checkout waits for calculateTotal. The script waits for checkout.top of the stackroundPrice(total)running nowcalculateTotal(cart)waits for roundPricecheckout(cart, coupon)waits for calculateTotalscriptwaits for checkout
The picture shows the moment line 9 calls roundPrice. Only the top frame runs. Each frame below it is a caller that waits for the frame above it to return.
A frame holds a context

A stack frame stands for one call that is still running. It points to that call's execution context. The context holds the parameters, the local variables, and the links to outer scopes. The frame also remembers where to go back to when the call returns. Two later lessons, Stack frames & calling conventions and Stack limits & stack overflow, show what engines store inside a frame.

Watch the stack grow and shrink

INTERACTIVE

The step-through above shows the code line by line. The playground below shows the same run as a pile of cards. Each card is one frame, and the newest card is at the top. The cards are drawn from the steps of the checkout run. They don't show the engine's real memory.

Watch the stack as a pile of cards
The checkout code from the run abovePop 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. The script calls checkout. Its frame is the first function frame.

Each card is one frame. The cards are drawn from the steps of the checkout run. They are not read from the engine's memory.
Real-life analogyA pile of cafeteria trays

In a cafeteria, clean trays sit in a pile. You put new trays on top, and you take trays from the top. You can't pull a tray out of the middle without lifting the ones above it. The last tray in is the first tray out.

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

Reading stack traces

STACK TRACE

A stack trace is a copy of the call stack, saved at the moment an error is made. It lists the frames that were on the stack at that moment.

An engine is the part of a browser or of Node.js that runs JavaScript. Chrome and Node.js use V8, Firefox uses SpiderMonkey, and Safari uses JavaScriptCore. Each engine writes a stack trace a little differently. But you read every trace the same way:

  1. Start at the top. The first frame is where the error started.
  2. Read down. Each frame below is the function that called the one above it.
Read a stack trace and measure the stack
The last function called throws an errorPop 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();
Outputtrace and depth
Click Show stack trace to see the trace for this code.

Not measured yet.

Try it yourself

Show stack trace prints the trace that Chrome and Node write for this code. Other browsers write it a little differently. Measure stack depth really runs in your browser: it counts how many calls fit before the stack is full.

The depth is different in each browser. It also changes with frame size, extensions, and settings. So treat the number as rough.

The table below takes that stack trace apart, one line at a time. Read each row as: "this line of the trace means this, and here is how to use it."

How to read each line of a stack trace from Chrome or Node
Line in the traceWhat it meansHow to use it
Error: Coupon database is offlineThe error's name and its message.Read it first, so you know what went wrong.
at applyCoupon (checkout.js:8:9)The top frame, the newest call. The error was made on line 8, at character 9.Look here first. This is usually where the error started.
at calculateTotal (checkout.js:5:3)The function that called applyCoupon, from line 5.Keep reading down to see which calls led to the error.
at checkout.js:10:1The top level of the script, on line 10. It has no function name.This bottom frame is the oldest call in the trace.
The stack trace Chrome and Node print for this codetext
Error: Coupon database is offline    at applyCoupon (checkout.js:8:9)    at calculateTotal (checkout.js:5:3)    at checkout (checkout.js:2:3)    at checkout.js:10:1
A stack trace read from the top down. Under the message Coupon database is offline, the first line is applyCoupon, where the error started. Below it, calculateTotal called applyCoupon. Below that, checkout called calculateTotal. At the bottom, the top level of the script called checkout. An arrow on the left says read down.Error: Coupon database is offlineapplyCouponerror started herecalculateTotalcalled applyCouponcheckoutcalled calculateTotaltop levelcalled checkoutread down
The first at line is the newest call. That is where the error started. Each line below it is the function that called the line above. So you read the trace from the top down.
Print the call path without an errorPop out in the code editor (opens in a new tab)JavaScript
function saveProfile() {
  validateProfile();
}
function validateProfile() {
  console.trace("profile validation");
}
saveProfile();

console.trace() prints a stack trace at the line where you call it. Nothing has to throw. In this example, line 5 calls it inside validateProfile. So the trace shows validateProfile on top and saveProfile under it. Each browser and Node print it in their own format, so your output may look a little different.

A function with no name is called an anonymous function. Anonymous functions make traces harder to read, because the trace has no name to show for them. So give your callbacks names. For example, write function showItem(item) { ... } as the callback. Then the trace shows showItem.

V8 also has extra tools that are not part of the JavaScript standard, such as Error.captureStackTrace and Error.stackTraceLimit. The later lesson Stack traces & the stack trace API covers them.

Stack overflow

MEASURE

The stack has a size limit. If calls keep going on top and none of them return, the stack fills up. This is called a stack overflow.

  • Chrome, Node, and Safari throw RangeError: Maximum call stack size exceeded.
  • Firefox may throw InternalError: too much recursion.

How many calls fit depends on the engine, the size of each frame, and the settings.

Real-life analogyA pile of sticky notes that reaches the ceiling

A normal pile of notes grows and shrinks as you start and finish tasks. Now imagine you keep starting new tasks and never finish one. The pile only grows. Soon it reaches the ceiling, and there is no room for one more note.

In real life: Every new note makes the pile taller
In JavaScript: Every call adds a frame
In real life: No note is ever torn off
In JavaScript: No call returns, so no frame comes off
In real life: The pile reaches the ceiling
In JavaScript: The engine throws a stack overflow error

The first example uses a setter. A setter is a function that runs when code assigns to a property. Writing set name(value) { ... } inside an object means: when code writes user.name = something, run this function.

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);}

Line 7 writes user.name = "Ada", so the setter runs. But line 3, inside the setter, writes to this.name again. That runs the same setter again, which writes again, and so on. The calls never end. The catch on line 8 catches the error, and line 9 prints its name, RangeError.

To fix it, store the value under a different name, such as this._name, or in a private field.

Two functions that call each other foreverPop out in the code editor (opens in a new tab)JavaScript
function first() {  second();}function second() {  first();}try {  first();} catch (error) {  console.log(error.name);}

Recursion means a function that calls itself. Here, two functions take turns: first calls second, and second calls first. This is called mutual recursion. It overflows for the same reason: no call ever returns.

Line 8 starts the loop inside a try block. When the stack is full, the catch catches the error, so the page keeps working. Line 10 prints RangeError.

A pile of frames that only grows. On the script frame sit first, second, first, second, and more. An arrow beside the pile points up and says keeps growing. At the top, the engine has no room for one more frame and throws RangeError.and many more...second()RangeErrorno room for one morefirst()second()first()scriptkeeps growing
first calls second, and second calls first. No call ever returns, so no frame comes off. The pile grows until the engine has no room left. Then it throws a RangeError.
Fun fact

A stack overflow doesn't need a function that calls itself. Any chain of calls that never ends can cause it, as long as each call happens right away. For example:

  • a setter that writes to its own property,
  • a getter that reads its own property,
  • or two functions that call each other forever.

The stack in DevTools

TRY IT

In the lesson Debugging in the browser, you paused code with breakpoints. A breakpoint is a line where DevTools pauses your code. You also stepped into and out of functions.

While the code is paused, DevTools shows a Call Stack panel. It is a live view of the call stack. The top entry is the function running now. The functions that called it 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, paste the code into the Console, and press Enter.
  2. The debugger; statement on line 8 pauses the code. DevTools opens the Sources panel (called Debugger in Firefox).
  3. Find the Call Stack panel. It lists applyCoupon, then calculateTotal, then checkout.
  4. Click each one. The Scope panel changes to show that frame's variables.

Some calls happen later, not right away. setTimeout asks the browser to run a function later. The function you hand to it is called a callback.

A timer callback starts on an empty stackPop out in the code editor (opens in a new tab)JavaScript
const logs = [];
function start() {
  logs.push("start");
  setTimeout(() => {
    logs.push("callback");
    console.log(logs.join(", "));
  }, 0);
  logs.push("end");
}
start();
  1. Line 10 calls start, and line 3 adds "start" to the list.
  2. Line 4 hands the callback to setTimeout. The callback doesn't run yet.
  3. Line 8 adds "end". Then start returns, and its frame comes off.
  4. Later, the browser calls the callback on lines 5 and 6. The start frame is gone by then, so the callback starts on an empty stack. It prints start, end, callback.

DevTools can still show which code set up the timer. This is called an async stack trace. DevTools builds that history for you. It is not one real stack, and the old frames are not still there. You will meet async stack traces again in the lesson Async stack traces.

Does it add or remove a frame?
  • A function call starts running
  • A function reaches return
  • A function throws an error and has no catch of its own
  • forEach calls your callback
  • const total = 3 + 4 runs
  • You click 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 helper functions. Say an error happens inside roundPrice. The stack tells you whether the bad value came from calculateTotal or from applyCoupon. Or say a callback fails inside forEach. The stack tells you which of your functions started the forEach.

The four tools below are easy to mix up. Read each row as: "use this tool when you want to know this."

Which debugging tool answers which question
ToolWhat question it answersWhen to use it
Call stackWhere am I, and which functions called me?When you step through functions that call other functions.
Stack traceWhere did this error start?When you read error logs and bug reports.
Console logsWhat value did this line make?For quick checks while you write code.
BreakpointsWhat does the program know right now?When you want to pause and look at the variables.

When you meet an error, try these steps:

  1. Find the first frame that is in your own code, and read that line.
  2. Read down the stack to find which function passed in the bad value.
  3. Look at the variables in that frame, instead of guessing.
  4. Run the same path again with a breakpoint on the line where the value changes. Named callbacks make the path easier to follow.

The stack shows the path through your functions. It doesn't tell you whether a value is right. So use it together with a breakpoint, or a console.log of the value you care about.

Common misconceptions

"The stack keeps every variable forever."

No. A frame only stays while its call is running. When the function returns, its frame is gone. A closure can keep some of its variables alive, but not the frame.

"A stack trace lists the oldest call first."

No. Most JavaScript traces list the newest call first. Read down to go back through the callers.

"Throwing an error leaves the frames on the stack."

No. A throw removes frames one by one until a catch handles the error. If nothing catches it, the program reports the error.

"A function can only have one frame."

No. Each call gets its own frame. A function that calls itself can show up on the stack many times.

"Clicking a frame in DevTools goes back in time."

No. Clicking an older frame only changes what you look at. It doesn't run the program again or undo anything.

A throw removes frames until it reaches catchPop out in the code editor (opens in a new tab)JavaScript
function loadCart() {
  console.log("before");
  throw new Error("offline");
}
try {
  loadCart();
} catch (error) {
  console.log("caught");
}

Line 6 calls loadCart. Line 2 prints "before", and line 3 throws an error. loadCart has no catch of its own, so JavaScript removes its frame and keeps looking. It finds the catch on line 7, and line 8 prints "caught".

The stack trace saved in the error still lists loadCart. But that frame is no longer on the stack.

Practice: read the stack

5 EXERCISES
Exercise 1 · Warm-upPredict the output

Write what the console prints, 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. Which function did the error start in?

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

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

      Exercise 3 · PracticeFix the setter that calls itself

      The setter writes to this.name, which runs the setter again. Which property should it write to instead?

      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 stack depth

        Click Measure stack depth in the playground in the Reading stack traces section. Round the number to 1000, 10000, or 100000, whichever is closest, and type it.

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

          Exercise 5 · ChallengeName a callback so traces are easier to read

          Which callback name will 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

            For each question, ask two things. Which frame is on top right now? And when does the function underneath go on? Each answer explains why it is right or wrong.

            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 stack trace from Chrome or Node, which frame comes first, right 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 7The setter on user calls itself until the stack is full. What error do Chrome, Node, and Safari throw?

              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 this program print? The callback has a name, showItem.

              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 frame onto the stack. Returning pops it off.
            • The top frame is the function running now. The functions that called it wait underneath.
            • A stack trace is a copy of the stack, saved when an error is made. Read it from the top down.
            • A stack overflow happens when calls keep going inside calls until the stack is full.
            • When code is paused, DevTools lets you click each frame to see its line and its variables.

            One-liner.
            The call stack is JavaScript's pile of running function calls: the last call in is the first one out.

            Up next: Hoisting & the temporal dead zone.

            CompleteFrontend Clear concepts. Working examples.