Chapter 2.3☕ 22 min read

Execution Contexts: Creation & Execution Phase

Har function call ek naya kamra kholta hai — andar kya hota hai, aaj seedha samjho.

01What is an Execution Context?

An Execution Context (EC) is V8's internal record for running a piece of JavaScript code. Think of it as a workspace where the engine stores everything needed to execute that code.

Every script execution creates a Global Execution Context. Every function call creates a new Function Execution Context. Each EC has three parts:

Variable Environment  → where variables and functions live
Lexical Environment   → scope chain reference
this binding          → what this refers to

V8 maintains an Execution Context Stack (call stack) — it follows LIFO (Last In, First Out). When a function is called: push new EC. When it returns: pop EC. Only one EC runs at a time — JavaScript is single-threaded.

// Each function call = new Execution Context pushed to stack

function greet(name) {    // EC3 created and pushed
  return 'Hello ' + name;
} // EC3 popped

function main() {         // EC2 created and pushed
  const result = greet('Sai'); // EC3 pushed while EC2 pauses
  console.log(result);
} // EC2 popped

// Global EC1 always at bottom
main(); // EC2 pushed

// Stack at peak: [EC1(global), EC2(main), EC3(greet)]
// After greet returns: [EC1(global), EC2(main)]
// After main returns: [EC1(global)]
The execution context stack IS the call stack you see in DevTools. Each frame in the stack trace corresponds to one execution context. "Maximum call stack size exceeded" means the stack grew too large — too many nested ECs without any returning.
02Global Execution Context

Created once when the script starts running, the Global Execution Context is the foundation everything else builds on.

Its Variable Environment contains all var declarations and function declarations — hoisted here during the creation phase. Its this binding refers to the global object: window in browser, global in Node.js, {} in ES modules. The Global EC stays alive until the page or process ends.

In strict mode, the global EC's this is undefined inside strict functions. ES modules have their own top-level scope — they are not truly global.

// This is the global execution context running this code:
var city = 'Hyderabad';      // goes into global Variable Environment
function sayHi() { return 'Hi!'; } // function declaration — fully hoisted

console.log(city);   // 'Hyderabad' — accessible from global
console.log(sayHi); // [Function: sayHi] — accessible from global

// In browser: window.city === 'Hyderabad' (var leaks to global)
// In Node.js: global.city === 'Hyderabad'

// let/const do NOT go into global object:
let secret = '123';
// window.secret === undefined (not on global object)

console.log(this); // In browser: Window object
                   // In Node script: {} (module wrapper)
                   // In Node REPL: global
Why this matters: var and function declarations in the global EC attach to the global object (window). let and const do NOT — they live in the global lexical environment but not on window. This is why window.myVar works for var but not for let. Always use let/const to avoid polluting the global object.
03Function EC: Creation Phase

When a function is called, V8 creates a new Function Execution Context. But before any code in the function runs, the Creation Phase executes:

Creation Phase steps (before any code runs):

1. Create Arguments object (for non-arrow functions)
2. Hoist var declarations (set to undefined)
3. Hoist function declarations (fully initialized)
4. Set up scope chain (reference to outer lexical environment)
5. Determine this binding

THEN the Execution Phase begins — code runs line by line.

function example() {
  // CREATION PHASE (before this line runs):
  // var x → undefined
  // function inner → fully initialized (hoisted!)
  // this → determined

  // EXECUTION PHASE (runs line by line):
  console.log(x);      // undefined — hoisted but not assigned yet
  console.log(inner);  // [Function: inner] — fully hoisted!
  // console.log(y);   // ReferenceError — let not hoisted usably

  var x = 10;
  let y = 20;          // TDZ ends here — accessible after this line

  function inner() { return 'I was hoisted!'; }

  console.log(x); // 10 — now assigned
  console.log(y); // 20 — now accessible
}
example();
Hoisting is NOT the JS engine physically moving code. It is a result of the two-phase execution model: creation phase scans the function body and registers declarations, THEN execution phase runs the code. The scan happens before any code executes — that's why function declarations are available before their line.
04this Binding in Execution Contexts

this is determined during the Creation Phase, NOT at function definition time. There are 5 rules (in priority order):

1. new keyword       → this = newly created object
2. call/apply/bind   → this = first argument (explicit)
3. obj.method()      → this = object before the dot (implicit)
4. plain call        → this = global (sloppy) or undefined (strict)
5. Arrow function    → no own this — inherits from enclosing lexical context
// Rule 3: Implicit binding
const user = {
  name: 'Sai',
  greet() { return 'Hello, ' + this.name; }
};
user.greet(); // this = user ✅

// Rule 4: Default binding (sloppy mode)
function show() { return this; }
show(); // this = window (browser) or global (Node)

// Rule 2: Explicit binding
function greet() { return 'Hello, ' + this.name; }
greet.call({ name: 'Rahul' }); // this = { name: 'Rahul' }

// Rule 5: Arrow — no own this
const obj = {
  name: 'Sai',
  greet: () => 'Hello, ' + this.name // ← this from OUTER scope!
};
obj.greet(); // 'Hello, undefined' — this is NOT obj!

// Rule 1: new keyword
function Person(name) { this.name = name; }
const p = new Person('Sai'); // this = newly created object
p.name; // 'Sai'
The most common this bug: passing a method as a callback. const fn = user.greet; fn() — now it's a default binding call, not implicit. this is no longer user. Fix: fn = user.greet.bind(user) or use arrow function in class body.
05EC Stack: Tracing Code Flow

Understanding the EC stack explains: hoisting, closures, this, recursion limits. It's not theoretical — you use it every time you debug.

Practical debugging: DevTools call stack = EC stack at that moment. Every async operation (setTimeout, fetch) creates a NEW call stack when the callback runs — async callbacks don't inherit the original EC.

Generator functions pause the EC (save to heap) and resume it later.

// Trace the EC stack for this code:
function c() {
  console.trace(); // Print current call stack
  return 'c done';
}
function b() {
  return c();
}
function a() {
  return b();
}
a();

// Stack when console.trace() runs:
// c  ← current EC
// b
// a
// (anonymous) ← global EC

// Async: callback starts with fresh stack
setTimeout(() => {
  console.trace(); // Stack shows only:
  // (anonymous)   ← timeout callback EC
  // (anonymous)   ← event loop, NOT the original caller!
}, 0);
This is why async errors don't show the original call location in their stack trace. The setTimeout callback runs in a fresh execution context — it has no memory of who called setTimeout. This is why error monitoring tools (Sentry, LogRocket) use zone.js or async context tracking to preserve the original stack.

Lo kar liya — Key Points:

  • ✅ Every function call creates a new Execution Context — V8 pushes it on the call stack
  • ✅ Global EC is created once and lives until the page ends — var and function declarations here attach to window
  • ✅ Creation Phase runs BEFORE any code: hoists var (undefined) and function declarations (fully initialized)
  • this is determined at call time, not definition time — 5 rules decide which object this refers to
  • ✅ Arrow functions have NO own this — they inherit from the enclosing lexical execution context
  • ✅ Async callbacks start with a fresh call stack — they don't inherit the original caller's EC
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase