December 31 2024
Understanding the JavaScript Event Loop
How stack, microtasks, timers, and execution order actually fit together.
Andrews Ribeiro
Founder & Engineer
4 min Intermediate Frontend
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.loghappen 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:
- First, JavaScript finishes the current synchronous work.
- Then the environment drains the pending microtasks.
- 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.thencatchfinally- 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:
- Does this run now, as synchronous code?
- Does this go into the microtask queue?
- 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
awaitsends 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.thenruns beforesetTimeout(..., 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
- The event loop is not one magical queue. It is the coordination between synchronous code, microtasks, and the next task.
- `setTimeout(..., 0)` does not mean immediate execution. It only means the timer becomes eligible early.
- Microtasks like `Promise.then` and the continuation after `await` usually run before the next task.
- Execution order is much easier to debug when you classify work as now, microtask, or next task.
Practice checklist
Use this when you answer
- Can I explain the order between synchronous code, `Promise.then`, and `setTimeout(..., 0)`?
- Can I explain why a long chain of microtasks can also delay timers, events, and rendering?
- Can I connect the event loop to real bugs like delayed callbacks or a frozen UI?
- Can I explain this in an interview without relying on a memorized diagram?
You finished this article
Next step
Memory Without Mystery Next step →Share this page
Copy the link manually from the field below.