Skip to content

JavaScript Execution Context & Call Stack Interview Questions

In the How JavaScript Works Behind the Scenes article, we saw what JavaScript does before and while it runs your code. Interviewers love this topic because it explains why a lot of weird outputs happen.

Let’s test it. For every question, try to work out the answer yourself before reading the explanation.

1. What is an execution context in JavaScript?

Section titled “1. What is an execution context in JavaScript?”

An execution context is the environment in which a piece of JavaScript code runs. It keeps track of the variables, the functions, and the value of this for that code.

There are two main types:

  1. Global Execution Context: It’s created once, when your script starts running.
  2. Function Execution Context: A new one is created every time a function is called.

Every execution context goes through two phases:

  1. Creation Phase: JavaScript scans the code and sets up memory. Variables declared with var get undefined, and function declarations get stored whole.
  2. Execution Phase: JavaScript runs the code line by line and assigns the real values.

2. Why does using a var before declaring it give undefined?

Section titled “2. Why does using a var before declaring it give undefined?”
console.log(city);
var city = "Delhi";
console.log(city);

Answer: undefined, then Delhi

In the above example, you can see that:

  1. In the Creation Phase, JavaScript sees var city and stores it in memory with the value undefined.
  2. In the Execution Phase, the first console.log runs before the assignment, so it prints undefined.
  3. Then city = "Delhi" runs, and the second console.log prints Delhi.

Notice there’s no error. city exists from the start, it just doesn’t have its value yet.

3. Why does using let before declaring it throw a ReferenceError?

Section titled “3. Why does using let before declaring it throw a ReferenceError?”
console.log(city);
let city = "Delhi";

Answer: ReferenceError: Cannot access 'city' before initialization

Hmm, you may be wondering why let behaves differently from var.

In the above example, you can see that:

  1. In the Creation Phase, JavaScript also knows about let and const variables.
  2. But unlike var, it doesn’t give them undefined. They’re left uninitialized.
  3. Until the line let city = "Delhi" actually runs, touching city throws a ReferenceError.

This is actually a good thing. With var, a bug like this silently gives you undefined. With let and const, you get an error right away and can fix it.

4. Does every function call get its own variables?

Section titled “4. Does every function call get its own variables?”
let count = 10;

function update() {
    let count = 0;
    count++;
    console.log(count);
}

update();
update();
console.log(count);

Answer: 1, 1, 10

In the above example, you can see that:

  1. Every call to update() creates a new Function Execution Context with its own count starting at 0.
  2. So both calls print 1. The second call doesn’t remember anything from the first one.
  3. The count inside update is a different variable from the global count. It shadows the global one inside the function.
  4. So the global count is never touched and stays 10.

5. Does a function use variables from where it’s called or where it’s written?

Section titled “5. Does a function use variables from where it’s called or where it’s written?”
const value = "global";

function printValue() {
    console.log(value);
}

function run() {
    const value = "local";
    printValue();
}

run();

Answer: global

Wait, what? printValue was called from inside run, where value is "local"!

In the above example, you can see that:

  1. When a function looks for a variable it doesn’t have, it looks in the place where it was written, not where it was called.
  2. printValue is written in the global scope, so it looks there and finds value = "global".
  3. The value inside run belongs only to run. printValue can’t see it.

This is called lexical scope, and it’s the same rule that makes closures work.

6. How does the call stack work in JavaScript?

Section titled “6. How does the call stack work in JavaScript?”
function first() {
    console.log("first start");
    second();
    console.log("first end");
}

function second() {
    console.log("second start");
    third();
    console.log("second end");
}

function third() {
    console.log("third");
}

first();

Answer:

first start
second start
third
second end
first end

In the above example, you can see that:

  1. The call stack keeps track of which function is running right now. It works like a stack of plates: the last one placed is the first one removed (LIFO).
  2. first() is pushed onto the stack. It logs, then calls second(), which gets pushed on top.
  3. second() logs, then calls third(), which gets pushed on top.
  4. third() logs and finishes, so it’s popped off. Control goes back to second(), which logs second end and gets popped.
  5. Finally, first() logs first end and gets popped.

The stack at its tallest looks like this:

| third()          |  ← running now
| second()         |
| first()          |
| Global Context   |

7. What causes “Maximum call stack size exceeded” in JavaScript?

Section titled “7. What causes “Maximum call stack size exceeded” in JavaScript?”
function countDown(num) {
    console.log(num);
    countDown(num - 1);
}

countDown(3);

Answer: It prints 3, 2, 1, 0, -1, ... thousands of times, then crashes with RangeError: Maximum call stack size exceeded.

In the above example, you can see that:

  1. Every call to countDown creates a new execution context and pushes it onto the call stack.
  2. Nothing ever tells the function to stop, so it keeps calling itself and nothing gets popped off.
  3. The call stack has a size limit. Once it’s full, JavaScript throws a RangeError. This is called a stack overflow.

The fix is a base case, which is a condition that stops the recursion:

function countDown(num) {
    if (num < 0) return; // base case
    console.log(num);
    countDown(num - 1);
}

8. How many execution contexts does JavaScript create?

Section titled “8. How many execution contexts does JavaScript create?”
function a() {}

function b() {
    a();
}

b();
b();

Answer: 5 in total, and the stack is never more than 3 deep.

In the above example, you can see that:

  1. One Global Execution Context is created when the script starts.
  2. The first b() call creates a context for b, and inside it, a() creates one more. That’s 2.
  3. The second b() call does the same thing again. That’s another 2.
  4. So 1 + 2 + 2 = 5 execution contexts are created in total.
  5. But at any moment, the call stack holds at most Global → b → a, which is 3 contexts.

Makes sense? A new context is created on every call, even when it’s the same function.

You practiced how to:

  • Explain execution contexts and their two phases
  • Predict what happens when you use var or let before declaring it
  • Tell which variable a function will use, based on where it was written
  • Trace the call stack and explain a stack overflow

Whenever an output looks weird, ask yourself two things. What happened in the Creation Phase, and what’s on the call stack right now?