Computer Engineering

JavaScript Event Loop Explained: How JavaScript Actually Executes Your Code

Learn how the JavaScript Event Loop works through visual explanations of the Call Stack, Web APIs, Callback Queue, Microtask Queue, Macrotasks, Promises, async/await, and rendering. Understand how browsers execute asynchronous JavaScript and why code runs in the order it does.

ByteAndBites·Jul 12, 2026
JavaScript Event Loop Explained: How JavaScript Actually Executes Your Code
JavaScript is often described as a single-threaded language.
That means it can execute only one piece of JavaScript code at a time.
Yet, modern web applications can:
  • Fetch data from APIs
  • Handle button clicks
  • Play videos
  • Animate interfaces
  • Execute timers
  • Update the UI
  • Download files
...all seemingly at the same time.

How is that possible?

The answer is the JavaScript Runtime.

JavaScript itself isn't doing all these tasks simultaneously. Instead, it relies on the browser (or Node.js) to perform asynchronous work while the Event Loop coordinates when JavaScript should execute the resulting callbacks.

Understanding this execution model is essential for writing performant JavaScript and reasoning about asynchronous code.

The JavaScript Runtime

JavaScript doesn't run in isolation. It runs inside an environment such as a browser or Node.js.
A simplified browser runtime looks like this:

Blog image
Each component has a specific responsibility.

The Call Stack

The Call Stack is where JavaScript executes synchronous code.
Whenever a function is called, it's pushed onto the stack.
When the function finishes, it's removed.
Think of it like stacking books.
The last book you place on the stack is the first one you remove.

Call Stack
┌─────┐
 │ sayHello()    │
├─────┤
 │ main()          │
├─────┤
 │ Global()       │
└─────┘
As functions return, the stack unwinds until it's empty.

JavaScript Runs to Completion

Consider this code:
console.log("A");
console.log("B");
console.log("C");
Output:
A
B
C
Nothing surprising.
JavaScript executes each statement synchronously before moving to the next.
No other task can interrupt the Call Stack.
This principle is called Run-to-Completion.

Then How Does setTimeout() Work?

Let's look at this example.
console.log("Start");
setTimeout(() => {
  console.log("Timeout");
}, 0);
console.log("End");
Many beginners expect:
Start
Timeout
End
Actual output:
Start
End
Timeout
Why?
Because setTimeout() isn't handled by JavaScript itself.

Web APIs

Functions such as:
  • setTimeout
  • fetch
  • DOM Events
  • requestAnimationFrame
are provided by the browser.
When JavaScript encounters:
setTimeout(callback, 1000);
The callback is handed over to the browser.
The browser starts the timer while JavaScript continues executing the remaining code.
Blog image
JavaScript never waits for the timer.

Callback Queue (Macrotask Queue)

When an asynchronous task completes, its callback enters the Callback Queue.
Examples include:
  • setTimeout
  • setInterval
  • DOM Events
  • MessageChannel
These callbacks don't execute immediately.
They wait until the Call Stack becomes empty.

Callback Queue
┌──── ─┐
 │ Timer             │
├── ───┤
 │ Click Event.  │
├── ───┤
 │ Message       │
└── ───┘


Enter the Event Loop

The Event Loop is surprisingly simple.
It repeatedly asks one question:
"Is the Call Stack empty?"
If the answer is No, it waits.
If the answer is Yes, it moves the next task into the Call Stack.
Blog image
This tiny loop powers asynchronous JavaScript.

Promises Introduce Microtasks

Now consider:
console.log("Start");
Promise.resolve().then(() => {
  console.log("Promise");
});
console.log("End");
Output:
Start
End
Promise
Promises don't go into the Callback Queue.
They enter the Microtask Queue.

Microtask Queue

Microtasks have higher priority than macrotasks.
Examples include:
  • Promise.then
  • catch
  • finally
  • queueMicrotask
  • MutationObserver
Microtask Queue
┌───  ────┐
 │ Promise.then        │
├─────  ──┤
 │ queueMicrotask.  │
└───  ────┘

Microtasks vs Macrotasks

This is one of the most important concepts in JavaScript.
Execution order:
  1. Run synchronous code
  2. Execute all microtasks
  3. Render (if needed)
  4. Execute one macrotask
  5. Repeat
Script
Call Stack
Microtasks
Rendering
Macrotask
Repeat


Why Promises Run Before setTimeout

Example:
console.log("A");
setTimeout(() => console.log("Timeout"));
Promise.resolve().then(() => console.log("Promise"));
console.log("B");
Output:
A
B
Promise
Timeout
Even though setTimeout() appears first, the Promise callback executes earlier because microtasks always have priority over macrotasks.

Async/Await Is Just Promises

Many developers think async/await is different.
It isn't.
async function load() {
  await fetch("/users");
  console.log("Done");
}
Behind the scenes, await pauses the function and schedules the remaining code as a microtask once the Promise resolves.

Browser Rendering

One common misconception is that the browser renders after every line of JavaScript.
It doesn't.
Rendering happens between Event Loop iterations, after the current task and all queued microtasks have completed.

Script
Microtasks
Render
Macrotask
Repeat

This is why long-running JavaScript can freeze the UI.
If the Call Stack never empties, the browser never gets a chance to paint.

A Complete Example

Consider:
console.log("1");
setTimeout(() => console.log("2"));
Promise.resolve().then(() => console.log("3"));
console.log("4");
Let's follow the execution.
  1. console.log("1")
  2. Register timer with the browser
  3. Schedule Promise as a microtask
  4. console.log("4")
  5. Call Stack becomes empty
  6. Execute all microtasks
  7. Render
  8. Execute timer callback
Final output:
1
4
3
2

Common Mistakes

❌ Thinking setTimeout(fn, 0) runs immediately
It doesn't. It waits until the current task completes and the Event Loop schedules the callback.
❌ Assuming Promises are synchronous
Promises are asynchronous but execute through the Microtask Queue, giving them higher priority.
❌ Blocking the Event Loop
Heavy synchronous work prevents timers, user interactions, and rendering from occurring.

Browser vs Node.js

The browser and Node.js both implement an Event Loop, but their environments differ.
Browsers provide Web APIs such as timers, Fetch, and DOM events.
Node.js provides its own runtime APIs, including file system operations, networking, and different Event Loop phases.
The core idea remains the same:
  • Execute synchronous code.
  • Process microtasks.
  • Handle queued asynchronous work.

Key Takeaways

  • JavaScript is single-threaded.
  • The Call Stack executes synchronous code.
  • Browsers handle asynchronous operations through Web APIs.
  • The Event Loop coordinates when callbacks are executed.
  • Microtasks always run before macrotasks.
  • Rendering occurs between Event Loop cycles.
  • Understanding the Event Loop makes asynchronous JavaScript much easier to reason about.

Conclusion

The JavaScript Event Loop isn't just an implementation detail. It's the foundation of how modern web applications remain responsive while handling asynchronous work. Once you understand how the Call Stack, Web APIs, microtasks, macrotasks, and rendering fit together, concepts like Promises, async/await, timers, and UI performance become much more intuitive.

JavaScript isn't truly multitasking. It creates the illusion of concurrency through a carefully orchestrated Event Loop that keeps your applications responsive while executing one task at a time.
Event loopSoftware EngineeringJavascriptGoogleBrowser
Systems Every Engineer Should Know
Series
Systems Every Engineer Should Know
View series
A deep-dive technical series explaining the engineering concepts behind modern software systems. From distributed systems and browser internals to scalability, networking, databases, and real-time architectures, each article breaks down complex topics using visuals, animations, real-world examples, and production-grade system design patterns. Learn how technologies used by companies like Netflix, Uber, Figma, and Discord actually work under the hood — without unnecessary jargon, theory overload, or textbook-style explanations.