DEV Community

Cover image for JavaScript Execution Context Made Simple
Shafiq Ur Rehman
Shafiq Ur Rehman

Posted on • Edited on

JavaScript Execution Context Made Simple

A JavaScript engine is a program that converts JavaScript code into a Binary Language. Computers understand the Binary Language. Every web browser contains a JavaScript engine. For example, V8 is the JavaScript engine in Google Chrome.

Let's dive in!

Diagram showing synchronous vs asynchronous JavaScript execution

  • Execution context: Execution Context is the environment in which JS code runs. It decides what variables and functions are accessible, and how the code executes. It has two types (Global & Function) and works in two phases (Memory Creation & Code Execution).

1. Global Execution Context (GEC)

This is created once when your script starts. It's the outermost context where:

  • Global variables and functions are stored
  • this refers to the global object (like window in browsers)

2. Function Execution Context (FEC)

When you call a function, a new context is created specifically for that function. It manages:

  • The function's local variables
  • The value of this inside the function
  • Arguments passed to the function

3. Memory Creation Phase

This is the first phase of an execution context. During this phase:

  • All variables and functions are allocated in memory
  • Functions are fully hoisted (stored with their complete code)
  • Variables declared with var are hoisted and initialized with undefined
  • Variables declared with let and const are also hoisted but remain uninitialized, staying in the Temporal Dead Zone (TDZ) until their declaration is reached

4. Code Execution Phase

This is the second phase, where:

  • The code executes line by line
  • Variables receive their actual values
  • Functions are called when invoked

The Variable Environment is a part of the Execution Context.
It is where all variables, functions, and arguments are stored in memory as key-value pairs during the Memory Creation Phase.

  • It includes:

    • Variable declarations
    • Function declarations
    • Function parameters

    It is used internally by the JS engine to track what's defined in the current scope.

  • Call stack: The call stack is a part of the JavaScript engine that helps keep track of function calls. When a function is invoked, it is pushed to the call stack, where its execution begins. When the execution is complete, the function is popped off the call stack. It utilizes the concept of stacks in data structures, following the Last-In-First-Out (LIFO) principle.

  • Event loop: The event loop runs indefinitely and connects the call stack, the microtask queue, and the callback queue. The event loop moves asynchronous tasks from the microtask queue and the callback queue to the call stack whenever the call stack is empty.

  • In JavaScript’s event loop, microtasks always have higher priority than macrotasks (callback queue).

  • Callback Queue (Macrotask Queue):  Callback functions for setTimeout() are added to the callback queue before they are moved to the call stack for execution.

Includes:

  • setTimeout()
  • setInterval()
  • setImmediate()
  • I/O events

  • Microtask queue: Asynchronous callback functions for promises and mutation observers are queued in the microtask queue before they are moved to the call stack for execution.

  • Includes things like:

    • Promise.then()
    • Promise.catch()
    • Promise.finally()
    • MutationObserver

Synchronous JavaScript

JavaScript is synchronous, blocking, and single-threaded. This means the JavaScript engine executes code sequentially—one line at a time from top to bottom—in the exact order of the statements.

Consider a scenario with three console.log statements.

console.log("First line");
console.log("Second line");
console.log("Third line");

Output:
First line
Second line
Third line
Enter fullscreen mode Exit fullscreen mode

Let's examine another example:

function getName(name) {
  return name;
}

function greetUser() {
  const userName = getName("Shafiq Ur Rehman");
  console.log(`Hello, ${userName}!`);
}

greetUser();

Enter fullscreen mode Exit fullscreen mode
  1. A new global execution context is created and pushed onto the call stack. This is the main execution context where the top-level code runs. Every program has only one global execution context, and it always stays at the bottom of the call stack.
  2. In the global execution context, the memory creation phase starts. In this phase, all variables and functions declared in the program are allocated space in memory (called the variable environment). Since we don’t have variables declared in the global scope, only the functions will be stored in memory.
  3. The function getName is stored in memory, with its reference pointing to the full function body. The code inside it isn’t executed yet—it will run only when the function is called.
  4. Similarly, the function greetUser is stored in memory, with its reference pointing to its entire function body.
  5. When the greetUser function is invoked, the code execution phase of the global execution context begins. A new execution context for greetUser is created and pushed on top of the call stack. Just like any execution context, it first goes through the memory allocation phase.
  6. Inside greetUser, the variable userName is allocated space in memory and initialized with undefined. (Note: During memory creation, variables declared with var are initialized with undefined, while variables declared with let and const are set as uninitialized, which leads to a reference error if accessed before assignment.)
  7. After the memory phase finishes, the code execution phase starts. The variable userName needs the result of the getName function call. So getName is invoked, and a new execution context for getName is pushed onto the call stack.
  8. The function getName allocates space for its parameter name, initializes it with undefined, and then assigns it the value "Shafiq Ur Rehman". Once the return statement runs, that value is returned to the greetUser context. The getName execution context is then popped off the call stack. Execution goes back to greetUser, where the returned value is assigned to userName. Next, the console.log statement runs and prints:

    Hello, Shafiq Ur Rehman!
    

    Once done, the greetUser The execution context is also popped off the call stack.

  9. Finally, the program returns to the global execution context. Since there’s no more code left to run, the global context is popped off the call stack, and the program ends.

Asynchronous JavaScript

Unlike synchronous operations, asynchronous operations don't block subsequent tasks from starting, even if the current task isn't finished. The JavaScript engine works with Web APIs (like setTimeout, setInterval, etc.) in the browser to enable asynchronous behavior.

Using Web APIs, JavaScript offloads time-consuming tasks to the browser while continuing to execute synchronous operations. This asynchronous approach allows tasks that take time (like database access or file operations) to run in the background without blocking the execution of subsequent code.

Let’s break this down with a setTimeout() example. (I’ll skip memory allocation here since we already covered it earlier.)

console.log("first");

setTimeout(() => {
  console.log("second");
}, 3000);

console.log("third");

Enter fullscreen mode Exit fullscreen mode

Here’s what happens when this code runs:

  1. The program starts with a global execution context created and pushed onto the call stack.
  2. The first line console.log("first") runs. It creates an execution context, prints "first" to the console, and then is popped off the stack.
  3. Next, the setTimeout() function is called. Since it’s a Web API provided by the browser, it doesn’t run fully inside the call stack. Instead, it takes two arguments: a callback function and a delay (3000ms here). The browser registers the callback function in the Web API environment, starts a timer for 3 seconds, and then setTimeout() itself is popped off the stack.
  4. Execution moves on to console.log("third"). This prints "third" immediately, and that context is also popped off.
  5. Meanwhile, the callback function from setTimeout is sitting in the Web API environment, waiting for the 3-second timer to finish.
  6. Once the timer completes, the callback doesn’t go straight to the call stack. Instead, it’s placed into the callback queue. This queue only runs when the call stack is completely clear. So even if you had thousands of lines of synchronous code after setTimeout, they would all finish first.
  7. The event loop is the mechanism that keeps watching the call stack and the queues. When the call stack is empty, the event loop takes the callback from the queue and pushes it onto the stack.
  8. Finally, the callback runs: console.log("second") prints "second" to the console. After that, the callback function itself is popped off, and eventually, the global execution context is cleared once everything has finished.

Conclusion

JavaScript runs code synchronously but can handle async tasks using browser Web APIs. Knowing how the engine works under the hood is key to mastering the language.

“Let me know your thoughts in the comments,” or “Follow me for more JavaScript insights.”

Top comments (3)

Collapse
 
hamza_ramay_9d644e39e0f22 profile image
Hamza Ramay

More Detailed Version With the Help of Claude AI

JavaScript Execution Context - Complete Guide with Visual Diagrams

What is JavaScript Engine?

Think of JavaScript Engine as a translator. Your computer only understands 0s and 1s (binary), but you write JavaScript code in English-like syntax. The JavaScript engine converts your code into binary so the computer can understand it.

Your JS Code → JavaScript Engine → Binary Code → Computer Executes
Enter fullscreen mode Exit fullscreen mode

Popular JavaScript Engines:

  • V8 (Google Chrome, Node.js)
  • SpiderMonkey (Firefox)
  • JavaScriptCore (Safari)

What is Execution Context?

Execution Context is like a box or container where your JavaScript code runs. Think of it as a room where all the variables, functions, and code execution happens.

┌─────────────────────────────────────┐
│        EXECUTION CONTEXT            │
│                                     │
│  ┌─────────────────────────────────┐│
│  │     Variables Storage           ││
│  │  var name = "Ali"               ││
│  │  var age = 25                   ││
│  └─────────────────────────────────┘│
│                                     │
│  ┌─────────────────────────────────┐│
│  │     Functions Storage           ││
│  │  function sayHello() {...}      ││
│  └─────────────────────────────────┘│
│                                     │
│  ┌─────────────────────────────────┐│
│  │     Code Execution              ││
│  │  Line 1: console.log("Hello")   ││
│  │  Line 2: var x = 10             ││
│  └─────────────────────────────────┘│
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Types of Execution Context

1. Global Execution Context (GEC)

This is the main room of your program. It's created when your JavaScript file starts running.

┌─────────────────────────────────────┐
│       GLOBAL EXECUTION CONTEXT      │
│              (Main Room)            │
│                                     │
│  • Created when program starts      │
│  • Only ONE exists per program      │
│  • Contains global variables        │
│  • Contains global functions        │
│  • 'this' points to window object   │
│                                     │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Example:

var globalVar = "I'm global!";

function globalFunction() {
    console.log("I'm a global function!");
}

console.log("This runs in Global Context");
Enter fullscreen mode Exit fullscreen mode

2. Function Execution Context (FEC)

Every time you call a function, a new room (execution context) is created just for that function.

┌─────────────────────────────────────┐
│    FUNCTION EXECUTION CONTEXT       │
│         (Function's Room)           │
│                                     │
│  • Created when function is called  │
│  • Each function call = new context │
│  • Contains function's variables    │
│  • Contains function's parameters   │
│  • Has its own 'this' value        │
│                                     │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Example:

function myFunction(name) {
    var localVar = "I'm local to this function!";
    console.log("Hello " + name);
}

myFunction("Ahmad"); // New execution context created
Enter fullscreen mode Exit fullscreen mode

Two Phases of Execution Context

Every execution context works in 2 phases:

Phase 1: Memory Creation Phase (Hoisting)

In this phase, JavaScript reserves memory for all variables and functions before executing any code.

MEMORY CREATION PHASE
┌─────────────────────────────────────┐
│             MEMORY                  │
│                                     │
│  Variables (var):                   │
│  ┌─────────────────────────────────┐│
│  │ name: undefined                 ││
│  │ age: undefined                  ││
│  └─────────────────────────────────┘│
│                                     │
│  Functions:                         │
│  ┌─────────────────────────────────┐│
│  │ sayHello: [Complete Function]   ││
│  │ calculate: [Complete Function]  ││
│  └─────────────────────────────────┘│
│                                     │
│  let/const variables:               │
│  ┌─────────────────────────────────┐│
│  │ email: <uninitialized>          ││
│  │ phone: <uninitialized>          ││
│  └─────────────────────────────────┘│
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Example:

console.log(name); // undefined (not error!)
console.log(sayHello); // [Function: sayHello]
console.log(email); // ReferenceError!

var name = "Hassan";
let email = "hassan@email.com";

function sayHello() {
    return "Hello!";
}
Enter fullscreen mode Exit fullscreen mode

Phase 2: Code Execution Phase

Now JavaScript executes the code line by line and assigns actual values.

CODE EXECUTION PHASE
┌─────────────────────────────────────┐
│         LINE BY LINE EXECUTION      │
│                                     │
│  Line 1: var name = "Hassan"        │
│  ┌─────────────────────────────────┐│
│  │ name: "Hassan" ✓                ││
│  └─────────────────────────────────┘│
│                                     │
│  Line 2: let email = "hassan@.."    │
│  ┌─────────────────────────────────┐│
│  │ email: "hassan@email.com" ✓     ││
│  └─────────────────────────────────┘│
│                                     │
│  Line 3: sayHello()                 │
│  → Creates new Function Context     │
│                                     │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Call Stack - The Manager

Call Stack is like a stack of plates. JavaScript uses it to keep track of which function is currently running.

        CALL STACK (LIFO - Last In, First Out)

┌─────────────────────────────────────┐
│                                     │  ← Top
├─────────────────────────────────────┤
│                                     │
├─────────────────────────────────────┤
│                                     │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │  ← Bottom (Always present)
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Detailed Example with Call Stack:

function first() {
    console.log("Inside first function");
    second();
    console.log("Back in first function");
}

function second() {
    console.log("Inside second function");
    third();
    console.log("Back in second function");
}

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

console.log("Start");
first();
console.log("End");
Enter fullscreen mode Exit fullscreen mode

Step-by-step Call Stack:

Step 1: Program starts
┌─────────────────────────────────────┐
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘

Step 2: first() is called
┌─────────────────────────────────────┐
│         first() context             │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘

Step 3: second() is called from first()
┌─────────────────────────────────────┐
│        second() context             │
├─────────────────────────────────────┤
│         first() context             │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘

Step 4: third() is called from second()
┌─────────────────────────────────────┐
│         third() context             │
├─────────────────────────────────────┤
│        second() context             │
├─────────────────────────────────────┤
│         first() context             │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘

Step 5: third() finishes, gets removed
┌─────────────────────────────────────┐
│        second() context             │
├─────────────────────────────────────┤
│         first() context             │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘

Step 6: second() finishes, gets removed
┌─────────────────────────────────────┐
│         first() context             │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘

Step 7: first() finishes, gets removed
┌─────────────────────────────────────┐
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Output:

Start
Inside first function
Inside second function
Inside third function
Back in second function
Back in first function
End
Enter fullscreen mode Exit fullscreen mode

Synchronous vs Asynchronous JavaScript

Synchronous JavaScript (Default Behavior)

JavaScript runs one line at a time, like reading a book page by page.

SYNCHRONOUS EXECUTION
┌─────────────────────────────────────┐
│  Line 1  →  Line 2  →  Line 3       │
│    ↓         ↓         ↓            │
│  Wait      Wait      Wait           │
│   for        for       for          │
│ Line 1    Line 2    Line 3          │
│ to finish to finish to finish       │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Example:

console.log("First");    // Executes first
console.log("Second");   // Waits for first to complete
console.log("Third");    // Waits for second to complete

// Output:
// First
// Second
// Third
Enter fullscreen mode Exit fullscreen mode

Asynchronous JavaScript (Non-blocking)

Some operations (like setTimeout, API calls) don't block the next line.

ASYNCHRONOUS EXECUTION
┌─────────────────────────────────────┐
│  Line 1  →  Line 2 (Async)  →  Line 3  │
│    ↓           ↓                ↓    │
│  Executes   Goes to Web API   Executes│
│ immediately    Environment   immediately│
│                    ↓                  │
│              Comes back later         │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Example:

console.log("First");

setTimeout(() => {
    console.log("Second (after 2 seconds)");
}, 2000);

console.log("Third");

// Output:
// First
// Third
// Second (after 2 seconds)
Enter fullscreen mode Exit fullscreen mode

Event Loop - The Traffic Controller

Event Loop is like a traffic controller that manages synchronous code and asynchronous code.

           EVENT LOOP SYSTEM

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│ Call Stack  │    │ Web APIs    │    │ Callback    │
│             │    │             │    │ Queue       │
│ [empty]     │    │ setTimeout  │    │             │
│             │    │ HTTP req    │    │ [callback1] │
│             │    │ DOM events  │    │ [callback2] │
└─────────────┘    └─────────────┘    └─────────────┘
       ↑                  │                   │
       │                  ↓                   │
       └──────── Event Loop checks: ──────────┘
                "Is Call Stack empty?"
                "Move callback to Call Stack"
Enter fullscreen mode Exit fullscreen mode

Detailed Example:

console.log("Start");

setTimeout(() => {
    console.log("Timeout 1");
}, 0);

setTimeout(() => {
    console.log("Timeout 2");
}, 0);

console.log("End");
Enter fullscreen mode Exit fullscreen mode

Step-by-step execution:

Step 1: console.log("Start")
┌─────────────┐
│Call Stack   │
│console.log  │ → Output: "Start"
└─────────────┘

Step 2: setTimeout (first one)
┌─────────────┐    ┌─────────────┐
│Call Stack   │    │Web APIs     │
│setTimeout   │ →  │Timer (0ms)  │
└─────────────┘    │+ callback1  │
                   └─────────────┘

Step 3: setTimeout (second one)
┌─────────────┐    ┌─────────────┐
│Call Stack   │    │Web APIs     │
│setTimeout   │ →  │Timer (0ms)  │
└─────────────┘    │+ callback1  │
                   │+ callback2  │
                   └─────────────┘

Step 4: console.log("End")
┌─────────────┐
│Call Stack   │
│console.log  │ → Output: "End"
└─────────────┘

Step 5: Timers complete, callbacks move to queue
┌─────────────┐    ┌─────────────┐
│Call Stack   │    │Callback     │
│[empty]      │    │Queue        │
└─────────────┘    │[callback1]  │
                   │[callback2]  │
                   └─────────────┘

Step 6: Event Loop moves callbacks to Call Stack
┌─────────────┐ → Output: "Timeout 1"
│Call Stack   │
│callback1    │
└─────────────┘

┌─────────────┐ → Output: "Timeout 2"
│Call Stack   │
│callback2    │
└─────────────┘
Enter fullscreen mode Exit fullscreen mode

Final Output:

Start
End
Timeout 1
Timeout 2
Enter fullscreen mode Exit fullscreen mode

Microtasks vs Macrotasks (Advanced)

JavaScript has two types of queues:

                 PRIORITY SYSTEM
┌─────────────────────────────────────────────────────┐
│                                                     │
│  ┌─────────────────┐         ┌─────────────────┐    │
│  │ MICROTASK QUEUE │  HIGH   │ MACROTASK QUEUE │    │
│  │   (Priority)    │ PRIORITY│    (Normal)     │    │
│  │                 │         │                 │    │
│  │ • Promise.then  │         │ • setTimeout    │    │
│  │ • Promise.catch │         │ • setInterval   │    │
│  │ • async/await   │         │ • DOM events    │    │
│  └─────────────────┘         └─────────────────┘    │
│           │                           │              │
│           └─────────┬─────────────────┘              │
│                     ↓                                │
│              EVENT LOOP CHECKS:                      │
│          "Microtasks first, then Macrotasks"         │
└─────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Example:

console.log("Start");

setTimeout(() => {
    console.log("Timeout");
}, 0);

Promise.resolve().then(() => {
    console.log("Promise");
});

console.log("End");

// Output:
// Start
// End
// Promise    ← Microtask (higher priority)
// Timeout    ← Macrotask (lower priority)
Enter fullscreen mode Exit fullscreen mode

Complete Real-world Example

Let's trace through a complex example:

var globalVar = "I'm global";

function outerFunction(x) {
    var outerVar = "I'm outer";

    function innerFunction(y) {
        var innerVar = "I'm inner";
        console.log(globalVar + ", " + outerVar + ", " + innerVar);
        console.log("x = " + x + ", y = " + y);
    }

    innerFunction(20);
}

console.log("Program starts");
outerFunction(10);
console.log("Program ends");
Enter fullscreen mode Exit fullscreen mode

Memory Creation Phase:

GLOBAL EXECUTION CONTEXT - Memory Phase
┌─────────────────────────────────────┐
│ globalVar: undefined                │
│ outerFunction: [Complete Function]  │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Execution Trace:

Step 1: Global Context Created
┌─────────────────────────────────────┐
│      GLOBAL EXECUTION CONTEXT      │
│  globalVar = "I'm global"           │
│  outerFunction = [function]         │
└─────────────────────────────────────┘

Step 2: outerFunction(10) called
┌─────────────────────────────────────┐
│     OUTER FUNCTION CONTEXT         │
│  x = 10                             │
│  outerVar = "I'm outer"             │
│  innerFunction = [function]         │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │
└─────────────────────────────────────┘

Step 3: innerFunction(20) called
┌─────────────────────────────────────┐
│     INNER FUNCTION CONTEXT         │
│  y = 20                             │
│  innerVar = "I'm inner"             │
├─────────────────────────────────────┤
│     OUTER FUNCTION CONTEXT         │
│  x = 10, outerVar = "I'm outer"     │
├─────────────────────────────────────┤
│      GLOBAL EXECUTION CONTEXT      │
│  globalVar = "I'm global"           │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Output:

Program starts
I'm global, I'm outer, I'm inner
x = 10, y = 20
Program ends
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Execution Context = Environment where code runs
  2. Global Context = Main room (created once)
  3. Function Context = New room for each function call
  4. Two Phases: Memory Creation → Code Execution
  5. Call Stack = Manages function calls (LIFO)
  6. Event Loop = Manages sync and async code
  7. Microtasks have higher priority than Macrotasks

Understanding these concepts helps you:

  • Debug your code better
  • Understand hoisting
  • Write better asynchronous code
  • Avoid common JavaScript pitfalls

Remember: JavaScript is single-threaded but can handle multiple tasks through the Event Loop and Web APIs!

Collapse
 
im-shafiqurehman profile image
Shafiq Ur Rehman

Great

Collapse
 
hamza_ramay_9d644e39e0f22 profile image
Hamza Ramay

Execution Context Visualizer : claude.ai/public/artifacts/99e8017...