Inside JavaScript’s Brain: How Async Code Really Works

shouvik sarkarshouvik sarkar
7 min read

Hey there, JS heroes! Have you ever wondered?

  1. If JavaScript is synchronous by nature, then how does it manage to run asynchronous code like async/await, .then, and .catch?

  2. What exactly happens to the setTimeout() function before it gets executed?

     console.log("Start the code");
    
     setTimeout(() => {
       console.log("This runs after 2 seconds");
     }, 2000);
    
     console.log("finish the code");
    
     Start the code
     finish the code
     This runs after 2 seconds
    
  3. And why on earth does setTimeout still get delayed even when the delay is set to 0ms?

     console.log("Start the code");
    
     setTimeout(() => {
       console.log("This is set Time out");
     }, 0);
    
     console.log("finish the code");
    
     Start the code
     finish the code
     This is set Time out
    
  4. Why promise gets executed before setTimeout()?

     console.log("Start script");
     setTimeout(() => {
       console.log("I am set timeout");
     }, 0);
    
     Promise.resolve().then(() => {
       console.log("I am promise.");
     });
     console.log("End script");
    

If these questions have ever popped into your head, good news! 🎉 You’re about to get all the answers.

So buckle up, grab some coffee and let’s dive into the fascinating world of JavaScript’s async execution.

how does JS execute asynchronous code?

As we all know JS is a single threaded language or synchronous language. That means it executes a code line by line from top to bottom. It doesn’t skip a code line to be executed later.

But we all know that setTimeOut function (or similar asynchronous codes) gets skipped and executed later on (depending on how much time is set). So how does that happen? 🤔

Here is the twist asynchronous codes and DOM are not part of JS engine, they are part of the Browser.

Yes then how does it get executed? An on top of that how does JS handle something that is not the part of JS engine itself?

Well Well, JS has it’s ways to do things that are not part of JS engine. Now you ask “how”?

Let’s see:

console.log("Start the code");

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

console.log("finish the code");

What happens when we write this code?

Step-1:

A GEC is created. That GEC has 2 phases,

  1. Memory Creation Phase (MCP)

  2. Execution Phase (EP)

It includes hoisting variables in MCP and the memory allocated for variables and functions,

Step-2:

The GEC is pushed into call stack. Why? When GEC is pushed inside call stack, it knows what code to run first.

As you can see in call stack GEC is pushed

Step-3

Now inside the call stack, the memory creation phase hoists the variable and the execution phase starts executing the code line by line

Line-1:

The first line get executed first console.log("Start the code"); .

Line-2:

The next line to be executed is the setTimeout code block

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

But since setTimeout is not a part of the JS engine, it does not get executed here.

Step-1:

  • So JS engine sends it to WebAPI.

  • Web API sets time to whatever the time was set to the setTimeout function. (2 sec in this case)

Step-2:

  • After 2 sec, once the time is completed, the WebApi pushes the code to Call Back Queue.

Line-3 (Parallelly):

While the WebApi is handling the async code and pushing that code to the Call Back Queue, JS Execution Context does not wait for that to be resolved and moves on to the next line to execute that.

Once the last line of code is executed, the GEC gets deleted. Till then the Call Back function is stored in the Call Back Queue.

After call stack is empty

The Event Loop is the mechanism that watches the Call Stack and the Callback Queue. When the Call Stack is empty, it pushes the next task ( callback) from the Callback Queue into the Call Stack.

And then the call stack executes the CB function. And we get the last output of the code.

But when the timer is 0?

console.log("Start the code");

setTimeout(() => {
  console.log("This is set Time out");
}, 0);

console.log("finish the code");

OK what about this one? Although the timer is set 0 sec. But still why the outcome is same as before. Why?

If you were paying close attention, you should’ve already gotten the answer. If you haven’t gotten it yet, let’s find out.

Just like before the first line will be executed first.

Then the 2nd line or block of code is sent to the WebAPI. And since the timer is 0 sec, immediately the code is sent to the Call Back Queue.

You might think “Well if the code was immediately sent to the Call Back Queue then how come it is printed after the 3rd line of code?”.

Well the reason is Event loop can not push code from Call Back Queue to Call Stack until Call Stack is empty.

And Since, the 3rd line is still there to be executed. Event Loop can not push the code to the Call Stack. Once the 3rd line is executed. CB function is sent to the Call Stack and then executed.

Here's the summary:

  1. Line 1: console.log("Start the code") is executed.

  2. Line 2: setTimeout(..., 0) is handed to the WebAPI.

  3. WebAPI pushes it to the Task Queue immediately.

  4. Line 3: console.log("finish the code") is executed.

  5. Only then, the Event Loop sees the empty Call Stack and pushes the timeout callback from the Task Queue.

That is why even with 0 sec time, the CB function is executed after the 3rd line.

Example-3:

console.log("Start script");
setTimeout(() => {
  console.log("I am set timeout");
}, 0);

Promise.resolve().then(() => {
  console.log("I am promise.");
});
console.log("End script");
Start the code
finish the code
I am promise.
This is set Time out

You may ask, “Well what now? Why .then() is being resolved before setTimeout() ? And how does .then() get executed?”

So let’s learn.

1st line and setTimeot()

Just like before the first line is executed and then the setTimeout is sent to the WebAPI and since the timer is 0 sec, it’ll be resolved immediately and will be pushed to the Task Queue (sometimes called the Callback Queue). Since the call Stack is not empty yet, Event Loop will wait before pushing the code back to the Call Stack.

Promise:

After the setTimeout() it is time for the Promise to be resolved and executed.

Just like setTimeout() , Promise also not the part of JS Engine so as usual it is also sent to the WebAPI. Since here The Promise is immediately resolved( Promise.resolve().then()) , it’s pushed to the Queue. But Promises are not sent to the Call Back Queue (aka Task Queue) It is pushed to the Micro Task Queue.

And just like Task Queue, Event Loop does not push the code of the Micro Task Queue to the callStack until it is empty.

Then once the last line is executed, The stack becomes empty. Now it’s time for the Event Loop to push the code from Task Queue and Micro Task Queue. The Event Loop always checks the Microtask Queue first and only moves on to the Task Queue once the Microtask Queue is completely empty. So when time comes Event Loop Pushes the code from Micro Task Queue to the Call Stack.

After that code is executed and deleted, Call Stack is again empty and then the code from Task Queue is pushed to the Call Stack.

And thus we get the output we got earlier.

Conclusion:

Hopefully, many of your questions were answered in this article. If you’ve made it this far — great job! But remember, reading alone isn’t enough. To truly understand how JavaScript handles asynchronous code, open your browser console and start experimenting.

Try different combinations of setTimeout, Promises, console.log, and more. Notice the order of execution and test your understanding. That hands-on practice is where the real learning happens!

The more you explore these concepts, the better you'll understand performance, responsiveness, and debugging in modern JS applications.

Connect With Me: Twitter LinkedIn GitHub

20
Subscribe to my newsletter

Read articles from shouvik sarkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

shouvik sarkar
shouvik sarkar