JavaScript Execution Context: A Complete Guide with Examples

  • Programming Languages

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
  • 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:

  1. Memory Creation Phase
  2. 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 var are initialized as undefined
  • Function declarations are stored completely
  • let and const remain 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:

  1. Is the Call Stack empty?
  2. 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.