Skip to main content

Command Palette

Search for a command to run...

The JavaScript Event Loop

Published
4 min readView as Markdown
G
I am a developer with learnings in many different languages, frameworks and technologies.

In this article, I will help you understand the JavaScript event loop.

So basically there are three things: the call stack, the webapis and the queues. Stop right there!, This is the worst way to understand it. There is a better and easier way to understand the same thing and go beyond the basics.

JavaScript is Single-Threaded:

Yes, it is 100% correct that JavaScript is single-threaded, but not in a way that people understand. By “JavaScript is single-threaded”, it means that the JavaScript language code runs on a call stack, which is single-threaded, which just executes functions from the stack and runs the statements. But then how does it handle asynchronous work? No, it does not handle that; rather, it delegates it to someone else.

The Async Magic:

The browser (or Node.js) provides APIs for tasks like timers, network requests, or file access. These APIs often use background threads or native processes under the hood, allowing JavaScript to offload asynchronous work while the main thread stays responsive.

The Event Loop:

The event loop itself is a scheduler outside of the call stack or the JavaScript language’s single thread; it decides what to run next. It uses the tick mechanism. It watches over 3 things:

  1. The Call Stack: It pushes and pops the functions from the call stack and tells JavaScript’s thread what to run next.

  2. The Microtask Queue: It inserts functions’ callbacks and promises, and when the call stack becomes empty, the event loop processes all queued microtasks like resolved promises before moving to the next macrotask. If new microtasks are added while processing existing ones, they’re also handled before continuing.

  3. The Macrotask Queue: Once the microtask queue is empty, the event loop runs the contents of the macrotask queue one by one in order; these include setTimeout, setInterval and I/O calls. Once one macrotask is completed(i.e Call Stack is empty), it again goes to check the microtask first before continuing with the next macrotask. (In other words, in one tick, only one macrotask gets executed.) Our starting point of the script is also one macrotask.

And that is the whole JavaScript event loop. It gives you the illusion that in a single thread, you are doing asynchronous activity. Now, to understand the context better, let me give you an example code snippet:

Example:

async function foo() {
  console.log("Inside foo start");
  await new Promise(res => setTimeout(res, 1000));
  console.log("Inside foo end");
}

foo();
console.log("Outside foo");

Here’s what happened:

  1. The initial main script gets added as a macrotask.

  2. foo function gets registered (not run yet)

  3. foo() call happened, so it will go in the call stack (Starts running it)

  4. print “Inside foo start”

  5. Promise call with await happening, so create a promise constructor and run the content, which is setTimeout, this is delegated to the Runtime Engine or WebAPI, which starts counting for 1000ms.

  6. As a promise is not resolved yet, it is marked as pending and will not be put inside the microtask queue.

  7. foo is entirely skipped due to the await keyword, and control is back to the macrotask.

  8. macrotask sees “Outside foo” console log and adds it to the call stack.

  9. The call stack is not empty, so it prints “Outside foo”

  10. The microtask is still empty because the promise is not resolved yet.

  11. Now the call stack is empty, and meanwhile the timer runs out and puts the resolve function in a macrotask as the timer has expired.

  12. macrotask’s resolve function resolves the promise and puts the remaining content of foo as a callback into the microtask queue.

  13. The macrotask is empty. Again, control back to the call stack, then to the microtask queue as the call stack is empty.

  14. microtask executes the resolved promise and pushes the remaining foo() content to the call stack.

  15. Again macrotask is empty, thus the tick is completed, and again the call stack is checked.

  16. The call stack has the remaining code of foo, which is executed.

  17. prints “Inside foo end”

These steps are coordinated by the event loop, which manages when code runs. The actual code execution still happens on JavaScript’s single main thread.

More from this blog

The iamgautam03 Blog

22 posts

A growing collection of articles on software development, system design (HLD & LLD), and practical coding tips. I focus on making complex ideas simple, so developers can learn faster and build better.