
Understand the JavaScript event loop in depth. Learn how the call stack, microtasks, and macrotasks work together to handle asynchronous code efficiently, keeping your applications fast, predictable, and responsive.
Martin Ferret
November 18, 2025
JavaScript runs on a single thread. You can think of it as a single-lane road where only one car, one piece of code, can drive at a time.
That car represents the call stack, the place where your currently executing functions live.
When a car needs to stop for gas, for example when a function triggers an asynchronous operation like fetch() or setTimeout(), it doesn’t block the entire road. It simply pulls over. Other cars can continue driving.
Once the gas is ready, the car rejoins the traffic flow through one of two queues: the microtask queue (for Promises, queueMicrotask, or MutationObserver) or the macrotask queue (for setTimeout, setInterval, or DOM events).
The event loop acts as the traffic controller.
It continuously checks if the road is clear. When it is, it takes the next waiting car from the queues and lets it pass, always giving priority to microtasks before macrotasks.
This system is what allows JavaScript to appear asynchronous, even though it never runs more than one task at a time.
The JavaScript runtime is built around three essential structures.
The Call Stack
This is where synchronous code executes. Each function call is pushed onto the stack and popped off when it completes.
The Heap
A large region of memory where objects, closures, and references are stored.
The Queues
These hold asynchronous tasks waiting to run once the call stack is empty.
The event loop monitors all of these. It ensures that tasks are processed in the right order, that the main thread never stays idle, and that asynchronous operations never block execution.
To understand how asynchronous code executes, you need to distinguish between microtasks and macrotasks.
Microtasks such as Promises and queueMicrotask are executed immediately after the current stack clears, before the next rendering cycle. They have higher priority and will always run before any macrotask begins.
Macrotasks such as timers, events, or I/O operations run after all microtasks have finished. Each macrotask represents a new “tick” of the event loop.
Example:
console.log('Start');
setTimeout(() => console.log('Macrotask: Timeout'), 0);
Promise.resolve().then(() => console.log('Microtask: Promise'));
console.log('End');
Output:
Start
End
Microtask: Promise
Macrotask: Timeout
The promise callback executes before the timeout because microtasks are always processed first.
Consider a common UI pattern. When a user clicks a button, you show a loading message and then fetch data from an API.
button.addEventListener('click', () => {
list.textContent = 'Loading...';
fetch('/api/items')
.then(res => res.json())
.then(data => {
list.textContent = data.join(', ');
});
});
The text “Loading…” appears immediately because setting it is a synchronous operation.
The fetch() call runs asynchronously, and when the data is ready, the promise resolves and updates the DOM.
The event loop coordinates this without freezing the interface or blocking user interaction.
Understanding the event loop is essential for writing efficient JavaScript.
Performance
It helps you prevent blocking operations that slow down rendering or user input.
Predictability
It allows you to reason about task order, avoiding subtle async timing bugs.
Frameworks
Every modern frontend or backend framework such as React, Angular, or Node.js relies on the event loop’s scheduling model.
Control
Once you understand microtask and macrotask behavior, you can deliberately control when and how asynchronous code runs.
The event loop is the heartbeat of the JavaScript runtime. It manages everything that happens, from synchronous calls to asynchronous operations, one step at a time.
When you master how it moves between the stack, microtasks, and macrotasks, asynchronous JavaScript stops feeling random. It becomes predictable, transparent, and fully under your control.
Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

State Management in React: useReducer, Context, and External Stores
Learn when to move beyond useState in React. This guide covers useReducer, the split-context pattern, external stores like Zustand, server state with TanStack Query, and useSyncExternalStore.
Aurora Scharff
Jun 4, 2026

Deploying Nuxt: Presets, Platforms, and Hybrid Rendering
How to deploy Nuxt to Vercel, Netlify, Cloudflare, and Node, with hybrid rendering via routeRules.
Reza Baar
Jun 3, 2026

Trigger options for @defer
Learn how Angular's @defer block triggers lazy loading and explore the different loading strategies available to control when components are rendered for better performance and user experience.
Alain Chautard
Jun 2, 2026