ToolPopToolPop
JavaScript · Lesson 9 of 18

Execution context, the call stack, and the hoisting trap

6 min readUpdated 24 Jun 2026

Every interviewer at some point asks, "what happens when JavaScript runs your file?" The honest answer is, it runs it twice. First a memory pass, then a code pass. If you have never thought of it that way, hoisting feels like magic. Once you do, every trap question becomes obvious.

This lesson is the foundation. Closures, this, the event loop, all of them sit on top of execution contexts. Get this clear and the rest of senior JS clicks.

What an execution context is

An execution context (EC) is the box JavaScript creates every time it runs some code. There are two kinds.

  • Global execution context. Created once when your file starts. Sets up window (or globalThis), this, and scans your top-level variables.
  • Function execution context. Created fresh every time a function is called. Sets up arguments, the function's own variables, and its own this.

Each EC runs in two phases.

  1. Memory creation phase. JS scans the code, allocates space for variables and functions. var gets undefined. let and const get a slot but no value (Temporal Dead Zone). Function declarations get their full body.
  2. Execution phase. JS runs the code line by line, top to bottom.

That two-pass behaviour is the whole reason hoisting exists. JS already knows your variable names before it executes line 1.

Diagram
rendering diagram...
Two-phase model of one execution context

The call stack, a stack of plates

JS is single-threaded. It can only do one thing at a time. The call stack is how it remembers which function it is currently inside.

Picture a stack of plates at a Bengaluru thali counter. Every function call adds a plate on top. Every return removes the top plate.

js
function placeOrder() {
  chargeUPI(499);
}
function chargeUPI(amount) {
  console.log("Debiting", amount);
}
placeOrder();

Step-by-step execution.

  1. Step 1. Global EC is pushed. JS scans, sees placeOrder and chargeUPI as full function bodies.
  2. Step 2. Hits placeOrder(). New EC for placeOrder is pushed onto the stack.
  3. Step 3. Inside it, chargeUPI(499) is called. A third EC pushed.
  4. Step 4. console.log runs, chargeUPI returns, its EC is popped.
  5. Step 5. placeOrder returns, popped. Global EC sits alone until the script ends.

Interview trap: if you ever see "Maximum call stack size exceeded", you have infinite recursion. The stack grew without ever popping.

Hoisting, the part that bites you

Hoisting is just the memory phase doing its job. It is not magic, it is not "moving code to the top". JS simply allocated the names before running line 1.

js
console.log(price);   // undefined, not ReferenceError
var price = 199;
 
console.log(qty);     // ReferenceError: Cannot access 'qty' before initialization
let qty = 2;
 
greet();              // "Namaste" — works
function greet() { console.log("Namaste"); }

The three rules.

  • var is hoisted and initialised as undefined. That is why console.log(price) prints undefined and not an error.
  • let and const are hoisted but uninitialised. They sit in the Temporal Dead Zone until the line that declares them runs. Touch them earlier and you get a ReferenceError.
  • Function declarations are fully hoisted. The whole function body is in memory before line 1, which is why you can call greet() above its definition.

Interview trap: var function expressions do NOT hoist the function body. console.log(add); var add = function(a,b){return a+b} prints undefined, then errors if you try to call add(1,2) on the next line.

Quick reference

Execution Context
The environment JS creates to run a piece of code, with its own variables, scope, and this.
Call Stack
LIFO data structure JS uses to track which function is currently executing.
Hoisting
The effect of the memory creation phase: variable and function names are known before code runs.
Temporal Dead Zone
The gap between a let or const being hoisted and the line that initialises it. Accessing the variable in this gap throws.

Predict the output

js
console.log(x);
console.log(food);
var x = 5;
let food = "biryani";

Step by step.

  1. Memory phase. x is set to undefined, food is hoisted into the TDZ.
  2. Execution phase. Line 1 prints undefined. Line 2 throws ReferenceError: Cannot access 'food' before initialization.

The script never even reaches var x = 5.

Senior rule: JS does two passes per execution context, memory first, then code. Hoisting is not weird. It is just the memory pass leaking into your mental model.

If you can draw the call stack on a whiteboard and label each EC with its variables and their hoisted state, you have already beaten 80% of interview candidates on this topic.

Quick quiz, prove you got it

0/3 answered
  1. 1.What does `console.log(x); var x = 5;` print?
  2. 2.What about `console.log(x); let x = 5;`?
  3. 3.Which is fully hoisted, body and all?

Interview insights

When asked "what is hoisting?"

  • Mention the two-pass model: memory creation phase, then execution phase
  • Distinguish var (initialised undefined) from let/const (in TDZ)
  • Mention that function declarations are fully hoisted but expressions are not

takeaway: Show the two phases, not just the catchphrase "hoisting moves declarations to the top".

Tricky questions they will ask

Q.Is hoisting actually "moving code to the top"?
common wrong answer:Yes, declarations move to the top of the file

A.No. Nothing physically moves. JS does a pass before execution where it scans for declarations and reserves memory. The actual code order in your file does not change.

Q.What is the order of two function declarations with the same name?

A.The last one wins. During the memory phase, JS overwrites the earlier reference with the later one. Both declarations get processed before any code runs.

Free tools you can use while you learn

Common questions

Q.What is the difference between hoisting var and let?
A.var declarations are hoisted and initialised to undefined. let and const are hoisted but stay in the Temporal Dead Zone, so accessing them before declaration throws ReferenceError.
Q.What is the call stack used for?
A.It is the data structure JavaScript uses to keep track of which function is currently running. Each function call pushes a new frame; returning pops it. Stack overflow happens when too many frames are pushed without popping.
Chai0/2 done

Watching quietly. Tap me if you want a tip.

JS Playground
console
// hit run to see output

Try this (0 of 2 done)

  1. 1

    Make the first log print undefined (not throw) by using var, then assign.

    show answer
    console.log(name);
    var name = 'Aarav';
    console.log(name);
  2. 2

    Trigger a ReferenceError by trying to log a let before its declaration. Wrap in try/catch to see the error message.

    hint

    let is in the Temporal Dead Zone until declared.

    show answer
    try { console.log(x); } catch(e) { console.log(e.message); }
    let x = 5;