Call Stack & Execution Context
Understand how JavaScript manages function execution, memory allocation, and the fundamental concepts that power the language's runtime.
Execution Context
An execution context is an abstract concept that holds information about the environment in which the current code is being executed. Think of it as a "container" that stores variables, functions, and the scope chain.
Types of Execution Context
Global Execution Context
Created when the script first runs. There's only one global context per program.
Function Execution Context
Created whenever a function is called. Each function has its own execution context.
Eval Execution Context
Created when code runs inside an eval() function (rarely used).
The Call Stack
The call stack is a LIFO (Last In, First Out) data structure that keeps track of execution contexts. When a function is called, its execution context is pushed onto the stack. When it returns, it's popped off.
Call Stack Visualization
function sayHello() {
console.log("Hello!");
}
function greet() {
sayHello();
console.log("Greetings complete");
}
greet();
// Call Stack sequence:
// 1. main() - global context
// 2. greet() pushed
// 3. sayHello() pushed
// 4. sayHello() popped (after console.log)
// 5. greet() popped
// 6. main() continues
Creation Phase
When an execution context is created, the JavaScript engine goes through the creation phase before executing any code:
Create Variable Object (VO)
Scans for variable declarations (var), function declarations, and function arguments.
Create Scope Chain
Determines what variables the context has access to, including parent scopes.
Determine "this" Binding
Sets the value of the "this" keyword based on how the function was called.
function example(x) {
var a = 10;
function inner() {
return a + x;
}
var b = inner();
}
// During creation phase:
// VO = {
// x: undefined, // argument
// a: undefined, // var declaration
// inner: fn, // function declaration
// b: undefined // var declaration
// }
Execution Phase
After the creation phase, the engine executes the code line by line, assigning values to variables and executing function calls.
function example(x) { // x = 5 assigned
var a = 10; // a = 10 assigned
function inner() {
return a + x;
}
var b = inner(); // inner() called, b = 15
}
example(5);
// After execution:
// VO = {
// x: 5,
// a: 10,
// inner: fn,
// b: 15
// }
Hoisting
Hoisting is JavaScript's behavior of moving declarations to the top of their scope during the creation phase. This is why you can use functions before they're declared.
Important Distinction
- Function declarations are fully hoisted (both declaration and definition)
- var variables are hoisted but initialized as undefined
- let/const are hoisted but remain in the "Temporal Dead Zone"
// Function hoisting
sayHi(); // Works! Output: "Hi!"
function sayHi() {
console.log("Hi!");
}
// var hoisting
console.log(x); // undefined (not ReferenceError)
var x = 5;
// let/const - Temporal Dead Zone
console.log(y); // ReferenceError!
let y = 10;
Stack Overflow
A stack overflow occurs when the call stack exceeds its maximum size, typically due to infinite recursion or extremely deep function calls.
// Infinite recursion - causes stack overflow
function forever() {
forever(); // Calls itself infinitely
}
forever(); // Error: Maximum call stack size exceeded
// Solution: Add a base case
function countdown(n) {
if (n <= 0) return; // Base case stops recursion
console.log(n);
countdown(n - 1);
}
countdown(5); // Works: 5, 4, 3, 2, 1
Key Takeaways
- Every function call creates a new execution context
- The call stack manages the order of execution
- Understanding hoisting helps avoid common bugs
- Always include base cases in recursive functions
- JavaScript is single-threaded, one call stack only