You Don’t Understand JavaScript Until You Understand the Event Loop
A few weeks ago, through a couple of posts, I explained how JavaScript’s Call Stack and its asynchronous behavior work (here and here). In this article, I’ll combine those two posts and expand on that explanation.
You’ve already used async/await.
You’ve already used Promises.
You’ve already written a setTimeout.
But do you really understand what’s happening underneath?
Understanding how the Event Loop works is a turning point in understanding how JavaScript works.
JavaScript is single-threaded
Maybe this is new to you, maybe you’ve heard it before. JavaScript executes one thing at a time.
It uses a structure called the Call Stack.
Whenever a function is called, it enters the stack. When it finishes, it leaves the stack. Simple as that.
But how does asynchrony work?
What happens when you do this?
Many people think that 0 means “execute immediately”.
But the output is:
A B C
Why?
Because setTimeout does not go to the Call Stack. It goes to the Task Queue.
What is the Task Queue?
Unlike the Call Stack, this is a queue.
The Task Queue is the structure where asynchronous executions are placed.
And it is divided into two queues: the Macro Task queue and the Micro Task queue. I’ll go deeper into that difference shortly.
The role of the Event Loop with the Task Queue
The Event Loop checks:
Is the Call Stack empty? If yes, it takes the next task from the queue.
So:
Synchronous code runs first. Then tasks enter the scene.
And this is where the important part begins.
Microtasks vs Macrotasks
Promises do not use the same queue as setTimeout.
They use the Microtask Queue.
Example:
Output:
A D C B
Why?
Actual order:
Execute synchronous code → A, D
Process ALL microtasks → C
Then execute macrotasks → B
After the Call Stack is emptied, the Event Loop executes all processes inside the Micro Task Queue. After that, it executes ONE task from the Macro Task Queue. Then it checks the Micro Task Queue again, after that the Call Stack, and a new cycle begins.
So the Microtask Queue has higher priority than the MacroTask Queue, but lower priority than synchronous code in the Call Stack.
This may explain why your setInterval never respects exactly the time you declared.
Why does this matter in practice?
Because it affects:
Classic example:
setState(1);
setState(2);
Depending on the context, this can be batched by React’s batching mechanism. I’ve written an article about that (here).
But if Promises are involved, the order changes.
async/await is not synchronous
When you code:
await fetchData();
You may think:
“Now the code waits.”
Not exactly.
What happens is:
The function returns a Promise. The rest of the function becomes a Microtask. The Event Loop coordinates the continuation.
So it looks synchronous, but it isn’t.
The impact on React
Concurrent Rendering. Transitions. Batching. Suspense.
None of this makes sense if you don’t understand:
When something enters the Microtask Queue When a render can be interrupted When something actually commits
React is a JavaScript library. Understanding JavaScript means understanding React.
The common mistake
When someone says:
“This behavior doesn’t make sense.”
It usually does.
It’s the Event Loop working correctly. You just might not fully understand how it works yet.
Conclusion
Understanding the Event Loop is not an academic detail.
It’s understanding:
Why your code executes in the order it does Why asynchronous bugs happen Why React behaves the way it does Why Promises have priority over setTimeout
You don’t need to memorize the algorithm.
But you need the mental model.
Because JavaScript isn’t magic.
It’s deterministic.
And the Event Loop is the conductor.