Logo
PortfolioAboutRSS
Back to all articles
JavaScript

Call Stack: How Function Execution Works

Learn how the JavaScript call stack works. Understand stack frames, LIFO ordering, execution contexts, and stack overflow errors.

Call Stack: How Function Execution Works

How does JavaScript keep track of which function is running? When a function calls another function, how does JavaScript know where to return when that function finishes?

The answer is the call stack. It's JavaScript's mechanism for tracking function execution.

function greet(name) {
  const message = createMessage(name)
  console.log(message)
}

function createMessage(name) {
  return "Hello, " + name + "!"
}

greet("Alice")  // "Hello, Alice!"

When greet calls createMessage, JavaScript remembers where it was in greet so it can return there after createMessage finishes. The call stack is what makes this possible.

What you'll learn in this guide:

  • What the call stack is and why JavaScript needs it
  • How functions are added and removed from the stack
  • What happens step-by-step when your code runs
  • Why you sometimes see "Maximum call stack size exceeded" errors
  • How to debug call stack issues like a pro

Prerequisite: This guide assumes basic familiarity with JavaScript functions. If you're new to functions, start there first!


The Stack of Plates: A Real-World Analogy

Imagine you're working in a restaurant kitchen, washing dishes. As clean plates come out, you stack them one on top of another. When a server needs a plate, they always take the one from the top of the stack, not from the middle or bottom.

        ┌───────────┐
        │  Plate 3  │  ← You add here (top)
        ├───────────┤
        │  Plate 2  │
        ├───────────┤
        │  Plate 1  │  ← First plate (bottom)
        └───────────┘

This is exactly how JavaScript keeps track of your functions! When you call a function, JavaScript puts it on top of a "stack." When that function finishes, JavaScript removes it from the top and goes back to whatever was underneath.

This simple concept, adding to the top and removing from the top, is the foundation of how JavaScript executes your code.


What is the Call Stack?

The call stack is a mechanism that JavaScript uses to keep track of where it is in your code. Think of it as JavaScript's "to-do list" for function calls, but one where it can only work on the item at the top.

function first() { second(); }
function second() { third(); }
function third() { console.log('Hello!'); }

first();
// Stack grows: [first] → [second, first] → [third, second, first]
// Stack shrinks: [second, first] → [first] → []

The LIFO Principle

The call stack follows a principle called LIFO: Last In, First Out.

  • Last In: The most recent function call goes on top
  • First Out: The function on top must finish before we can get to the ones below
LIFO = Last In, First Out

┌─────────────────┐
│   function C    │  ← Last in (most recent call)
├─────────────────┤     First to finish and leave
│   function B    │
├─────────────────┤
│   function A    │  ← First in (earliest call)
└─────────────────┘     Last to finish

Why Does JavaScript Need a Call Stack?

JavaScript is single-threaded, meaning it can only do one thing at a time. According to the ECMAScript specification, each function invocation creates a new execution context that gets pushed onto the stack. The call stack helps JavaScript:

  1. Remember where it is — Which function is currently running?
  2. Know where to go back — When a function finishes, where should execution continue?
  3. Keep track of local variables — Each function has its own variables that shouldn't interfere with others

ECMAScript Specification: According to the official JavaScript specification, the call stack is implemented through "execution contexts." Each function call creates a new execution context that gets pushed onto the stack.


How the Call Stack Works: Step-by-Step

Let's trace through a simple example to see the call stack in action.

A Simple Example

function greet(name) {
  const greeting = createGreeting(name);
  console.log(greeting);
}

function createGreeting(name) {
  return "Hello, " + name + "!";
}

// Start here
greet("Alice");
console.log("Done!");

Step-by-Step Execution

Program Starts

JavaScript begins executing your code from top to bottom. The call stack is empty.

Call Stack: [ empty ]

greet('Alice') is Called

JavaScript sees greet("Alice") and pushes greet onto the call stack.

Call Stack: [ greet ]

Now JavaScript enters the greet function and starts executing its code.

createGreeting('Alice') is Called

Inside greet, JavaScript encounters createGreeting(name). It pushes createGreeting onto the stack.

Call Stack: [ createGreeting, greet ]

Notice: greet is paused while createGreeting runs. JavaScript can only do one thing at a time!

createGreeting Returns

createGreeting finishes and returns "Hello, Alice!". JavaScript pops it off the stack.

Call Stack: [ greet ]

The return value ("Hello, Alice!") is passed back to greet.

greet Continues and Finishes

Back in greet, the returned value is stored in greeting, then console.log runs. Finally, greet finishes and is popped off.

Call Stack: [ empty ]

Program Continues

With the stack empty, JavaScript continues to the next line: console.log("Done!").

Output:

Hello, Alice!
Done!

Visual Summary

Step 1:          Step 2:          Step 3:              Step 4:          Step 5:

┌─────────┐      ┌─────────┐      ┌────────────────┐   ┌─────────┐      ┌─────────┐
│ (empty) │  →   │  greet  │  →   │createGreeting  │ → │  greet  │  →   │ (empty) │
└─────────┘      └─────────┘      ├────────────────┤   └─────────┘      └─────────┘
                                  │     greet      │
                                  └────────────────┘

Program          greet()          createGreeting()     createGreeting   greet()
starts           called           called               returns          returns
StepActionStack (top → bottom)What's Happening
1Start[]Program begins
2Call greet("Alice")[greet]Enter greet function
3Call createGreeting("Alice")[createGreeting, greet]greet pauses, enter createGreeting
4Return from createGreeting[greet]createGreeting done, back to greet
5Return from greet[]greet done, continue program
6console.log("Done!")[]Print "Done!"

Execution Context: What's Actually on the Stack?

When we say a function is "on the stack," what does that actually mean? Each entry on the call stack is called an execution context, sometimes referred to as a stack frame in general computer science terms. It contains everything JavaScript needs to execute that function.

Visualizing an Execution Context

┌─────────────────────────────────────────┐
│         EXECUTION CONTEXT               │
│         Function: greet                 │
├─────────────────────────────────────────┤
│  Arguments:    { name: "Alice" }        │
│  Local Vars:   { greeting: undefined }  │
│  this:         window (or undefined)    │
│  Return to:    line 12, main program    │
│  Outer Scope:  [global scope]           │
└─────────────────────────────────────────┘

Nested Function Calls: A Deeper Example

Let's look at a more complex example with multiple levels of function calls.

function multiply(x, y) {
  return x * y;
}

function square(n) {
  return multiply(n, n);
}

function printSquare(n) {
  const result = square(n);
  console.log(result);
}

printSquare(4);

Tracing the Execution

Step 1: Call printSquare(4)

Stack: [ printSquare ]

Step 2: printSquare calls square(4)

Stack: [ square, printSquare ]

Step 3: square calls multiply(4, 4)

Stack: [ multiply, square, printSquare ]

This is the maximum stack depth for this program: 3 frames.

Step 4: multiply returns 16

Stack: [ square, printSquare ]

Step 5: square returns 16

Stack: [ printSquare ]

Step 6: printSquare logs 16 and returns

Stack: [ empty ]

Output: 16

printSquare(4)     square(4)          multiply(4,4)       multiply          square           printSquare
called             called             called              returns 16        returns 16       returns

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐   ┌─────────────┐   ┌─────────┐
│printSquare  │ →  │   square    │ →  │  multiply   │ →  │   square    │ → │printSquare  │ → │ (empty) │
└─────────────┘    ├─────────────┤    ├─────────────┤    ├─────────────┤   └─────────────┘   └─────────┘
                   │printSquare  │    │   square    │    │printSquare  │
                   └─────────────┘    ├─────────────┤    └─────────────┘
                                      │printSquare  │
                                      └─────────────┘

Depth: 1           Depth: 2           Depth: 3           Depth: 2          Depth: 1          Depth: 0

Understanding the flow: Each function must completely finish before the function that called it can continue. This is why printSquare has to wait for square, and square has to wait for multiply.


The #1 Call Stack Mistake: Stack Overflow

The call stack has a limited size. The default limit varies by engine — Chrome's V8 typically allows around 10,000–15,000 frames, while Firefox's SpiderMonkey has a similar threshold. If you keep adding functions without removing them, eventually you'll run out of space. This is called a stack overflow, and JavaScript throws a RangeError when it happens.

┌─────────────────────────────────────────────────────────────────────────┐
│                         STACK OVERFLOW                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  WRONG: No Base Case                    RIGHT: With Base Case            │
│  ────────────────────                   ─────────────────────            │
│                                                                          │
│  function count() {                     function count(n) {              │
│    count()  // Forever!                   if (n <= 0) return  // Stop!   │
│  }                                        count(n - 1)                   │
│                                         }                                │
│                                                                          │
│  Stack grows forever...                 Stack grows, then shrinks        │
│  ┌─────────┐                            ┌─────────┐                      │
│  │ count() │                            │ count(0)│ ← Returns            │
│  ├─────────┤                            ├─────────┤                      │
│  │ count() │                            │ count(1)│                      │
│  ├─────────┤                            ├─────────┤                      │
│  │ count() │                            │ count(2)│                      │
│  ├─────────┤                            └─────────┘                      │
│  │  ....   │                                                             │
│  └─────────┘                                                             │
│  💥 CRASH!                              ✓ Success!                       │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

The Trap: Recursive functions without a proper stopping condition will crash your program. The most common cause is infinite recursion, a function that calls itself forever without a base case.

The Classic Mistake: Missing Base Case

// ❌ BAD: This will crash!
function countdown(n) {
  console.log(n);
  countdown(n - 1);  // Calls itself forever!
}

countdown(5);

What happens:

Stack: [ countdown(5) ]
Stack: [ countdown(4), countdown(5) ]
Stack: [ countdown(3), countdown(4), countdown(5) ]
Stack: [ countdown(2), countdown(3), countdown(4), countdown(5) ]
... keeps growing forever ...
💥 CRASH: Maximum call stack size exceeded

The Fix: Add a Base Case

// ✅ GOOD: This works correctly
function countdown(n) {
  if (n <= 0) {
    console.log("Done!");
    return;  // ← BASE CASE: Stop here!
  }
  console.log(n);
  countdown(n - 1);
}

countdown(5);
// Output: 5, 4, 3, 2, 1, Done!

What happens now:

Stack: [ countdown(5) ]
Stack: [ countdown(4), countdown(5) ]
Stack: [ countdown(3), countdown(4), countdown(5) ]
Stack: [ countdown(2), countdown(3), ..., countdown(5) ]
Stack: [ countdown(1), countdown(2), ..., countdown(5) ]
Stack: [ countdown(0), countdown(1), ..., countdown(5) ]
       ↑ Base case reached! Start returning.
Stack: [ countdown(1), ..., countdown(5) ]
Stack: [ countdown(2), ..., countdown(5) ]
... stack unwinds ...
Stack: [ countdown(5) ]
Stack: [ empty ]
✅ Program completes successfully

Error Messages by Browser

BrowserError Message
ChromeRangeError: Maximum call stack size exceeded
FirefoxInternalError: too much recursion (non-standard)
SafariRangeError: Maximum call stack size exceeded

Firefox uses InternalError which is a non-standard error type specific to the SpiderMonkey engine. Chrome and Safari use the standard RangeError.

Common Causes of Stack Overflow

Prevention tips:

  1. Always define a clear base case for recursive functions
  2. Make sure each recursive call moves toward the base case
  3. Consider using iteration (loops) instead of recursion for simple cases
  4. Be careful with property setters, use different internal property names

Debugging the Call Stack

When something goes wrong, the call stack is your best friend for figuring out what happened.

Reading a Stack Trace

When an error occurs, JavaScript gives you a stack trace, a snapshot of the call stack at the moment of the error.

function a() { b(); }
function b() { c(); }
function c() { 
  throw new Error('Something went wrong!'); 
}

a();

Output:

Error: Something went wrong!
    at c (script.js:4:9)
    at b (script.js:2:14)
    at a (script.js:1:14)
    at script.js:7:1

How to read it:

  • Read from top to bottom = most recent call to oldest
  • at c (script.js:4:9) = Error occurred in function c, file script.js, line 4, column 9
  • The trace shows you exactly how the program got to the error

Using Browser DevTools

Open DevTools

Press F12 or Cmd+Option+I (Mac) / Ctrl+Shift+I (Windows)

Go to Sources Tab

Click on the "Sources" tab (Chrome) or "Debugger" tab (Firefox)

Set a Breakpoint

Click on a line number in your code to set a breakpoint. Execution will pause there.

View the Call Stack

When paused, look at the "Call Stack" panel on the right. It shows all the functions currently on the stack.

Step Through Code

Use the step buttons to execute one line at a time and watch the stack change.

Pro debugging tip: If you're dealing with recursion, add a console.log at the start of your function to see how many times it's being called:

function factorial(n) {
  console.log('factorial called with n =', n);
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

The Call Stack and Asynchronous Code

You might be wondering: "If JavaScript can only do one thing at a time, how does it handle things like setTimeout or fetching data from a server?"

Great question! The call stack is only part of the picture.

When you use asynchronous functions like setTimeout, fetch, or event listeners, JavaScript doesn't put them on the call stack immediately. Instead, they go through a different system involving the Event Loop and Task Queue.

This is covered in detail in the Event Loop section.

Here's a sneak peek:

console.log('First');

setTimeout(() => {
  console.log('Second');
}, 0);

console.log('Third');

// Output:
// First
// Third
// Second  ← Even with 0ms delay, this runs last!

The setTimeout callback doesn't go directly on the call stack. It waits in a queue until the stack is empty. As Philip Roberts demonstrated in his acclaimed JSConf EU talk "What the heck is the event loop?" (viewed over 8 million times), this is why "Third" prints before "Second" even though the timeout is 0 milliseconds.


Common Misconceptions


Key Takeaways

The key things to remember about the Call Stack:

  1. JavaScript is single-threaded — It has ONE call stack and can only do one thing at a time

  2. LIFO principle — Last In, First Out. The most recent function call finishes first

  3. Execution contexts — Each function call creates a "frame" containing arguments, local variables, and return address

  4. Synchronous execution — Functions must complete before their callers can continue

  5. Stack overflow — Happens when the stack gets too deep, usually from infinite recursion

  6. Always have a base case — Recursive functions need a stopping condition

  7. Stack traces are your friend — They show you exactly how your program got to an error

  8. Async callbacks waitsetTimeout, fetch, and event callbacks don't run until the call stack is empty

  9. Each frame is isolated — Local variables in one function call don't affect variables in another call of the same function

  10. Debugging tools show the stack — Browser DevTools let you pause execution and inspect the current call stack


Test Your Knowledge


Frequently Asked Questions



Reference

Articles

Courses

graduation-cap

Introduction to Asynchronous JavaScript — Piccalilli

Part of the "JavaScript for Everyone" course by Mat Marquis. This free lesson explains why JavaScript is single-threaded, how the call stack manages execution contexts, and introduces the event loop and concurrency model. Beautifully written with a fun narrative style.

Videos

video

What the heck is the event loop anyway?

🏆 The legendary JSConf talk that made mass developers finally "get" the event loop. Amazing visualizations — a must watch!

video

The JS Call Stack Explained In 9 Minutes

Short, sweet, and beginner-friendly. Colt Steele breaks down the call stack with practical examples.

video

How JavaScript Code is executed? & Call Stack

Part of the popular "Namaste JavaScript" series. Akshay Saini explains execution with great visuals and examples.

video

Understanding JavaScript Execution

Shows how JavaScript creates execution contexts and manages memory during function calls. Part of Codesmith's excellent "JavaScript: The Hard Parts" series.

video

Javascript: the Call Stack explained

Traces through nested function calls line by line, showing exactly when frames are pushed and popped. Good for visual learners who want to see each step.

video

What is the Call Stack?

Uses a simple factorial example to demonstrate recursion on the call stack. Under 10 minutes, perfect for a quick refresher.

video

The Call Stack

Draws out the stack visually as code executes, making the LIFO concept easy to grasp. Includes a stack overflow example that shows what happens when things go wrong.

video

Call Stacks - CS50

Harvard's CS50 explains call stacks from a computer science perspective — great for understanding the theory.

video

Learn the JavaScript Call Stack

Live codes examples while explaining each concept, so you see exactly how to trace execution yourself. Great for following along in your own editor.

video

JavaScript Functions and the Call Stack

Focuses on the relationship between function invocation and stack frames. Explains why understanding the call stack helps you debug errors faster.

Join the conversation

Share your thoughts, react to this post, and upvote helpful comments.

Leave a comment

0/800 characters

Comments (0)

No comments yet. Be the first to share feedback.