Skip to main content

Understanding the JavaScript Event Loop

How stack, microtasks, timers, and execution order actually fit together.

Andrews Ribeiro

Andrews Ribeiro

Founder & Engineer

The problem

Many event loop explanations stop right before they become useful.

They hand you terms like:

  • call stack
  • microtask queue
  • macrotask queue

Then they act like naming those pieces already solved the problem.

But the real questions are usually more practical:

  • Why did this console.log happen before the other one?
  • Why did setTimeout(..., 0) not run “right away”?
  • Why does the code look async and still manage to block everything?

If the explanation does not help you predict execution order, it still has not turned into understanding.

Mental model

The most useful model is this:

  1. First, JavaScript finishes the current synchronous work.
  2. Then the environment drains the pending microtasks.
  3. Only after that does it pick the next scheduled task.

In plain English:

  • synchronous code runs now
  • a microtask runs right after the current block finishes
  • timers, events, and other scheduled work wait for the next turn

Short version:

The event loop decides what runs now, what runs right after, and what has to wait one more turn.

Breaking it down

First: what runs right now?

Synchronous code lives here.

Until that finishes:

  • timers do not run
  • events do not interrupt the middle of the block
  • microtasks do not cut in line

That is why a heavy synchronous block can still freeze everything, even inside codebases full of async and await.

Then: what becomes a microtask?

This includes things like:

  • Promise.then
  • catch
  • finally
  • the continuation after await

These callbacks are not running in parallel.

They simply get priority as soon as the current stack becomes empty.

Only then: what waits for the next task?

This includes things like:

  • setTimeout
  • I/O callbacks
  • environment events

That is why setTimeout(..., 0) still runs after pending microtasks.

The 0 does not mean “now.”

It only means “this timer can be considered early.”

The shortcut for predicting order

When you want to predict what runs first, ask only three questions:

  1. Does this run now, as synchronous code?
  2. Does this go into the microtask queue?
  3. Does this wait for the next task?

If you can classify the line that way, most of the “JavaScript magic” disappears.

Simple example

Look at this snippet:

console.log('start')

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

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

console.log('end')

The output is:

start
end
promise
timeout

Why?

  • console.log('start') runs immediately.
  • setTimeout(..., 0) schedules a future task.
  • Promise.resolve().then(...) schedules a microtask.
  • console.log('end') is still part of the current synchronous block.
  • When the stack becomes empty, the environment drains the pending microtasks.
  • Only after that does it take the next task.

So the 0 in setTimeout does not mean “immediate.”

It only means the timer becomes eligible early. It still has to wait for:

  • the current synchronous code to finish
  • the pending microtasks to run

Common mistakes

  • saying promises are “faster” than timers instead of explaining queue priority
  • treating setTimeout(..., 0) like a guarantee of immediate execution
  • assuming await sends the code to another thread
  • forgetting that too many microtasks can also delay timers, events, and rendering

How a senior thinks

More experienced engineers think less about the queue names and more about the practical consequence.

The reasoning usually sounds like this:

What is holding the main thread right now? Synchronous work? A long microtask chain? CPU-heavy code that should be split up?

That question is better because it leads to real action:

  • break up heavy work
  • reduce synchronous blocking
  • avoid scheduling microtasks without a reason
  • explain execution order without guessing

What the interviewer wants to see

In interviews, this topic is not really about showing off jargon.

What usually matters is whether you can:

  • predict the order of a small snippet without guessing
  • explain why Promise.then runs before setTimeout(..., 0)
  • connect the concept to a real bug, like delayed callbacks or a frozen UI

A strong answer usually sounds like this:

I separate what runs now, what becomes a microtask, and what waits for the next task. Once I do that, the execution order stops being guesswork.

The event loop is not there to impress people with vocabulary. It is there to help you predict behavior when the code starts acting strange.

Quick summary

What to keep in your head

Practice checklist

Use this when you answer

You finished this article

Next article Node Is Not Single-Threaded in the Way It Seems Previous article Writing Code People Can Understand

Keep exploring

Related articles