JavaScript powers everything from simple websites to complex web applications. But before writing advanced code, it is important to understand how JavaScript actually executes your code behind the scenes.
In this guide, you'll learn about the JavaScript Engine, Execution Context, Call Stack, Event Loop, and asynchronous behavior with practical examples and visual diagrams.
What Is the JavaScript Engine?
Think of the JavaScript Engine as a translator.
Your computer only understands binary language, which consists of 0s and 1s. JavaScript, however, is written in a human-readable syntax. The JavaScript engine converts your code into machine-readable instructions so the computer can execute it.
Your JS Code
↓
JavaScript Engine
↓
Binary Instructions
↓
Computer Executes
Popular JavaScript Engines
- V8 (Google Chrome, Node.js)
- SpiderMonkey (Firefox)
- JavaScriptCore (Safari)
What Is Execution Context?
An Execution Context is the environment where JavaScript code runs.
You can think of it as a container that holds:
- Variables
- Functions
- The code currently being executed
┌───────────────────────────────┐
│ EXECUTION CONTEXT │
│ │
│ Variables │
│ Functions │
│ Executing Code │
└───────────────────────────────┘
Whenever JavaScript runs code, it creates an execution context to manage that code.
Types of Execution Context
JavaScript creates different execution contexts depending on what is being executed.
1. Global Execution Context (GEC)
The Global Execution Context is created when a JavaScript program starts.
┌───────────────────────────────┐
│ GLOBAL EXECUTION CONTEXT │
│ │
│ • Created once │
│ • Contains global variables │
│ • Contains global functions │
│ • Exists throughout runtime │
└───────────────────────────────┘
Example
var globalVar = "I'm global";
function greet() {
console.log("Hello");
}
console.log("Running in Global Context");
2. Function Execution Context (FEC)
Every time a function is called, JavaScript creates a new execution context for that function.
┌───────────────────────────────┐
│ FUNCTION EXECUTION CONTEXT │
│ │
│ • Created on function call │
│ • Contains local variables │
│ • Contains parameters │
│ • Removed after execution │
└───────────────────────────────┘
Example
function greet(name) {
var message = "Welcome";
console.log(name);
}
greet("Ali");
When greet() is called, JavaScript creates a new execution context specifically for that function.
Two Phases of Execution Context
Every execution context goes through two phases:
- Memory Creation Phase
- Code Execution Phase
Phase 1: Memory Creation Phase
Before executing any code, JavaScript scans the program and allocates memory.
During this phase:
- Variables declared with
varare initialized asundefined - Function declarations are stored completely
letandconstremain uninitialized
Memory Phase
name → undefined
age → undefined
sayHello → Function
email → Uninitialized
Example
console.log(name); // undefined
console.log(sayHello); // function definition
console.log(email); // ReferenceError
var name = "Ali";
let email = "ali@email.com";
function sayHello() {
return "Hello";
}
This behavior is known as Hoisting.
Phase 2: Code Execution Phase
After memory allocation, JavaScript executes code line by line.
var name = "Ali";
let email = "ali@email.com";
sayHello();
Values replace undefined, functions execute, and new execution contexts are created whenever functions are called.
Understanding the Call Stack
JavaScript uses a Call Stack to keep track of function execution.
The Call Stack follows the LIFO (Last In, First Out) principle.
┌───────────────┐
│ Function C │ ← Top
├───────────────┤
│ Function B │
├───────────────┤
│ Function A │
├───────────────┤
│ Global Context│ ← Bottom
└───────────────┘
Example
function first() {
second();
}
function second() {
third();
}
function third() {
console.log("Inside Third");
}
first();
Stack Flow
Step 1
Global Context
Step 2
first()
Global Context
Step 3
second()
first()
Global Context
Step 4
third()
second()
first()
Global Context
Step 5
second()
first()
Global Context
Step 6
first()
Global Context
Step 7
Global Context
As functions complete, they are removed from the stack.
Synchronous JavaScript
By default, JavaScript executes code synchronously.
That means each line waits for the previous line to finish.
console.log("First");
console.log("Second");
console.log("Third");
Output
First
Second
Third
Execution occurs one line at a time.
Asynchronous JavaScript
Some operations take time to complete.
Examples include:
- API requests
- Timers
- File operations
- User interactions
Instead of blocking execution, JavaScript delegates these tasks and continues executing the next lines.
Example
console.log("First");
setTimeout(() => {
console.log("Second");
}, 2000);
console.log("Third");
Output
First
Third
Second
Even though the timeout appears before "Third", JavaScript continues executing while waiting for the timer.
Understanding the Event Loop
The Event Loop coordinates asynchronous operations.
It continuously checks:
- Is the Call Stack empty?
- Are there callbacks waiting?
If the stack is empty, callbacks are pushed onto the Call Stack.
┌─────────────┐
│ Call Stack │
└──────┬──────┘
│
▼
┌─────────────┐
│ Event Loop │
└──────┬──────┘
│
▼
┌─────────────┐
│ Callback │
│ Queue │
└─────────────┘
Event Loop Example
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
}, 0);
setTimeout(() => {
console.log("Timeout 2");
}, 0);
console.log("End");
Output
Start
End
Timeout 1
Timeout 2
Although both timers are set to 0ms, they still wait until the Call Stack becomes empty.
Microtasks vs Macrotasks
JavaScript maintains two queues.
Microtask Queue
Higher priority tasks:
- Promise.then()
- Promise.catch()
- async/await
Macrotask Queue
Normal priority tasks:
- setTimeout()
- setInterval()
- DOM events
Priority Order
Microtasks
↓
Macrotasks
The Event Loop always processes all microtasks before handling macrotasks.
Example
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output
Start
End
Promise
Timeout
Even though setTimeout appears first, the Promise callback executes earlier because it belongs to the Microtask Queue.
Complete Execution Context Example
Let's put everything together.
var globalVar = "I'm global";
function outerFunction(x) {
var outerVar = "I'm outer";
function innerFunction(y) {
var innerVar = "I'm inner";
console.log(globalVar);
console.log(outerVar);
console.log(innerVar);
console.log(x);
console.log(y);
}
innerFunction(20);
}
console.log("Program starts");
outerFunction(10);
console.log("Program ends");
Execution Flow
Global Context
globalVar = "I'm global"
outerFunction = function
outerFunction Context
x = 10
outerVar = "I'm outer"
innerFunction = function
innerFunction Context
y = 20
innerVar = "I'm inner"
Output
Program starts
I'm global
I'm outer
I'm inner
10
20
Program ends
This example demonstrates how nested functions can access variables from outer execution contexts.
Key Takeaways
- JavaScript code runs inside an Execution Context.
- There is only one Global Execution Context.
- Every function call creates a new Function Execution Context.
- Execution Contexts go through Memory Creation and Execution phases.
- The Call Stack manages function execution.
- JavaScript is single-threaded and synchronous by default.
- Asynchronous operations are handled using Web APIs and the Event Loop.
- Microtasks always execute before Macrotasks.
- Understanding Execution Contexts is the foundation for mastering closures, asynchronous programming, and advanced JavaScript concepts.
Once you understand Execution Contexts, the Call Stack, and the Event Loop, many JavaScript concepts become much easier to understand.
