Obafemi Emmanuel

JavaScript Event Loop & Execution Context: A Deep Dive

Published 3 months ago

JavaScript is a single-threaded, non-blocking, asynchronous language that uses the Event Loop to handle tasks efficiently. Understanding the Execution Context and how JavaScript manages asynchronous code is crucial for writing efficient applications.

In this blog, we will explore:

  • Call Stack
  • Web APIs
  • Callback Queue
  • Microtasks & Macrotasks

1. JavaScript Execution Context

The Execution Context is an environment where JavaScript code is executed. It consists of two main components:

  1. Memory Component (Variable Environment) – Stores variables and function declarations.
  2. Code Component (Thread of Execution) – Executes JavaScript code line by line.

Execution Context Lifecycle

When a function is called, JavaScript creates an Execution Context in three phases:

  1. Creation Phase: Memory is allocated for variables and functions.
  2. Execution Phase: JavaScript executes the code line by line.
  3. Destruction Phase: Once execution is completed, the context is removed.

2. Call Stack

The Call Stack is a data structure that keeps track of function execution. It follows the LIFO (Last In, First Out) principle.


How the Call Stack Works

  1. When a function is called, it is pushed onto the stack.
  2. Once the function execution is completed, it is popped off the stack.
  3. The process continues until the stack is empty.

Example:

function first() {
    console.log("First function");
}

function second() {
    first();
    console.log("Second function");
}

second();

Call Stack Process:

  • second() is pushed onto the stack.
  • Inside second(), first() is called and pushed onto the stack.
  • first() executes and is popped off the stack.
  • second() continues execution and is then popped off the stack.

3. Web APIs

JavaScript interacts with the Web APIs provided by the browser (like setTimeout, fetch, DOM manipulations, etc.) to handle asynchronous tasks.

Example of a Web API function:

console.log("Start");
setTimeout(() => {
    console.log("Inside setTimeout");
}, 2000);
console.log("End");

Execution Process:

  1. console.log("Start") runs immediately.
  2. setTimeout is sent to the Web API, which starts a timer.
  3. console.log("End") executes while the timer runs in the background.
  4. After 2 seconds, the callback function is moved to the Callback Queue.
  5. The Event Loop picks the task from the queue when the Call Stack is empty.

4. Callback Queue & Event Loop

The Callback Queue stores asynchronous callbacks waiting for execution. The Event Loop continuously checks if the Call Stack is empty, then pushes tasks from the Callback Queue to the Call Stack.


Example:

console.log("Start");
setTimeout(() => console.log("Timeout Callback"), 1000);
console.log("End");

Execution Order:

  1. console.log("Start") executes.
  2. setTimeout moves its callback to the Web API.
  3. console.log("End") executes.
  4. After 1 second, the callback moves to the Callback Queue.
  5. The Event Loop pushes the callback to the Call Stack when it is empty.
  6. console.log("Timeout Callback") executes.

5. Microtasks & Macrotasks

Microtasks

Microtasks are executed before macrotasks and include:

  • Promises (.then, .catch, .finally)
  • MutationObserver
  • queueMicrotask

Macrotasks

Macrotasks include:

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame

Example:

console.log("Start");

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

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

console.log("End");

Execution Order:

  1. console.log("Start") executes.
  2. setTimeout moves to the Web API (macrotask).
  3. Promise.resolve().then() moves to the Microtask Queue.
  4. console.log("End") executes.
  5. Microtask (Promise) executes before the macrotask (setTimeout).
  6. setTimeout executes last.

Output:

Start
End
Promise
setTimeout

Conclusion

Understanding the JavaScript Event Loop, Call Stack, Web APIs, Callback Queue, and Microtasks & Macrotasks is essential for writing efficient JavaScript code. Mastering these concepts will help you handle asynchronous operations effectively and avoid performance bottlenecks in your applications.


Leave a Comment


Choose Colour