javascript call stack and event loop
|

JavaScript Execution Context, Call Stack, Web API’s and Event Loop

JavaScript Execution Context

Execution Context in JavaScript is a whole lot of things. I’ll describe it as my understanding and show you how this works in running script behind the scenes.

There are two phases of Execution Context. Creation phase and execution phase. If you declare a variable in your code var myName = "codespoetry.com", the creation phase will allocate memory for it and create the variable like myName = undefined. It’s undefined because the engine is just allocating memory for it. However, if you use const or let it will create the variable with myName = uninitialized. That’s why var is hoisted and const, let aren’t. That’s a different topic though.

After the creation phase, it’s time to execute. The Call Stack, also called “Execution Stack”, is a part of the Execution Context and executes your script. So, for the above code, it’ll just assign the value “codespoetry.com” to myName. If you use const or let, those will initialize with the value “codespoetry.com”.

JavaScript is Synchronous

When you declare a function in your script, it gets initialized during the creation phase and executes in the execution phase. JavaScript is a single-threaded programming language that processes tasks synchronously, executing code line by line, top to bottom.

JavaScript can handle Asynchronous tasks, too

However, not all tasks can be handled synchronously. Sometimes, we need to perform asynchronous operations—like fetching data from a server, executing a function after a delay (e.g., in a clock app), or displaying a popup after a certain time following document load. Since JavaScript is non-blocking, it cannot pause execution while waiting for a response.

So, how does JavaScript handle asynchronous tasks like fetch(), setTimeout(), or interacting with the Geolocation API?

This is where the browser environment comes in. The browser provides Web APIs that allow JavaScript to perform tasks like:
– Fetching data from a server (fetch())
– Delaying execution (setTimeout() / setInterval())
– Interacting with system features (e.g., Geolocation API, DOM manipulation)

For example, when a script requests user consent for geolocation, the task is offloaded to the browser. The user sees a popup asking for permission, and only after approval does the API return the location (or false if denied). While this happens, the JavaScript engine continues executing other code without waiting for the response—ensuring smooth, non-blocking execution.

An example of non-blocking operations with Web APIs.

When a function, like setTimeout(), needs to delay execution, JavaScript uses Web APIs to handle the task. Here’s how it works:

  • When the Call Stack encounters a function that requires a delay (e.g., setTimeout() with a 1000ms delay), the function is offloaded to a Web API, and the Call Stack proceeds to the next task, like console.log("setTimeout executed")
  • After 1000ms, the Web API moves the delayed function to the Task Queue, waiting for execution.

Here, the browser tracks the time of 1000ms and pushes the callback function (to be executed after 1000ms) to the Task Queue. Web APIs aren’t limited to timers only. For more details, refer to the MDN Web APIs doc.

Handling Promise-based asynchronous operations

Unlike other async operations, JavaScript handles promise-based asynchronous tasks through a Microtask Queue. A system similar to the Task Queue but with higher priority. When it comes to handling asynchronous operations, such as a Promise resolved, its callback is added to the Microtask Queue.

Understanding the Event Loop

Now that we’ve covered the call stack, Task Queue, and Microtask Queue, let’s explore the event loop.

The event loop continuously monitors the Call Stack, Task Queue, and Microtask Queue. When the Call Stack is empty from executing synchronous task, it checks for pending tasks in the Task and Microtask queues:

  1. If tasks are in the Microtask Queue, the event loop prioritizes them and pushes them to the Call Stack for execution.
  2. Once all microtasks are processed, the event loop moves tasks from the Task Queue (if any) to the Call Stack.

By prioritizing the Microtask Queue over the Task Queue, JavaScript ensures a seamless and responsive user experience.

Bonus point

In my learning phase, after consuming all of these, a question popped into my mind: What will happen if count for the setTimeout (I’m giving a particular example) has ended and the Microtask Queue still has a long queue to push callbacks to the Call Stack? Remember, the Microtask Queue has priority over the Task Queue.

The answer is that JavaScript ensures the minimum time for executing the callback of setTimeout, not the exact time. So, it may delay the callback’s execution while the Microtask Queue has a long queue to push to the Call Stack

I hope it’s pretty clear how the Call StackEvent LoopTask Queue, and Microtask Queue work in the Execution Context. Let’s guess the result of the below code:

console.log("Hello World");

function fun() {
  console.log("Learning programming is fun");
}

function sum(a, b) {
  return a * b;
}

const promise = new Promise((resolve, reject) => resolve(10));

function result() {
  const sumResult = sum(5, 10);
  fun();
  setTimeout(() => console.log("After 1000ms of delay = ", sumResult), 1000);
}

async function promiseResult() {
  const result = await promise.then((data) => data);
  console.log("Promise Result = ", result);
}


result();
promiseResult();
console.log("Ending of execution");

Similar Posts

Leave a Reply