DEV Community

Cover image for JavaScript Event Loop Explained (Call Stack, setTimeout, Promise, async/await)
Rafsan Jany Ratul
Rafsan Jany Ratul

Posted on

JavaScript Event Loop Explained (Call Stack, setTimeout, Promise, async/await)

JavaScript is single-threaded, but it can still handle asynchronous operations like timers, API calls, and promises efficiently. This often confuses developers — especially when setTimeout, Promise, and async/await don’t execute in the order we expect.

In this article, we will break down how JavaScript actually works behind the scenes using the Call Stack, Web APIs, Callback Queue, Microtask Queue, and the Event Loop.

We will start with a simple synchronous example, then gradually move into asynchronous behavior using setTimeout, Promise, and async/await. By the end, you will clearly understand why some code runs immediately while other parts are delayed — and how JavaScript manages everything without blocking the main thread.

If you've ever wondered:

  • Why does setTimeout run last?
  • Why does Promise.then() run before setTimeout?
  • Does async/await make code synchronous?

Then this guide is for you

Example 1: Synchronous vs setTimeout

function a (){
    b()
    console.log("first")
}

function b (){
    d()
    console.log("second")
}

setTimeout(()=>{
    console.log("async task1")
},2000)

setTimeout(()=>{
    console.log("async task2") 
},1000)

function d (){
    e()
    console.log("fourth")
}

function e (){
    console.log("fifth")
}

a()
Enter fullscreen mode Exit fullscreen mode

How It Works

JavaScript is single-threaded, meaning it uses one Call Stack to execute code.

🔹 Step 1: Synchronous Execution

Functions run one by one:

a() → b() → d() → e()
Enter fullscreen mode Exit fullscreen mode

Output:

fifth
fourth
second
first
Enter fullscreen mode Exit fullscreen mode

🔹 Step 2: setTimeout Behavior

  • setTimeout is handled by Web APIs
  • After the timer finishes, it goes to the Callback Queue
  • The Event Loop moves it to the Call Stack when it's empty

Final Output:

fifth
fourth
second
first
async task2
async task1


Enter fullscreen mode Exit fullscreen mode

Event Loop (Simple Idea)

The Event Loop continuously checks:
“Is the Call Stack empty?”

If yes → it pushes tasks from the queue into the stack.


Example 2: Promise (Microtask Queue)

console.log("start")

setTimeout(() => {
  console.log("timeout")
}, 0)

Promise.resolve().then(() => {
  console.log("promise")
})

console.log("end")
Enter fullscreen mode Exit fullscreen mode

Output:

start
end
promise
timeout
Enter fullscreen mode Exit fullscreen mode

Why This Happens?

Because JavaScript has two queues:

🟡 Microtask Queue

  • Promise .then
  • async/await

🔵 Callback Queue (Macrotask)

  • setTimeout
  • setInterval

👉 Rule:

Microtasks always run before Callback Queue tasks.


Example 3: async/await

async function example() {
  console.log("start")
  await new Promise(resolve => setTimeout(resolve, 1000))
  console.log("end")
}

console.log("before")
example()
console.log("after")
Enter fullscreen mode Exit fullscreen mode

Output:

before
start
after
end
Enter fullscreen mode Exit fullscreen mode

Important Concept

Many people think:

async/await makes code synchronous

❌ That’s incorrect

✔️ Correct idea:

async/await makes asynchronous code LOOK synchronous, but it still uses the Event Loop.


Key Takeaways

  • JavaScript runs synchronous code first
  • Async tasks go to Web APIs
  • Event Loop manages execution
  • Promises use Microtask Queue
  • Microtasks run before setTimeout
  • async/await is just syntax over Promise

Final Thought

JavaScript never pauses the entire program for async code.

It simply schedules it smartly using:

  • Call Stack
  • Event Loop
  • Queues

Once you understand this, async JavaScript becomes much easier


If this helped you, feel free to share

Top comments (0)