Event Loop & Task Queues in Javascript
In this lesson, we learn about the core engine of JavaScript that makes asynchronous operations possible. The event loop ensures JavaScript remains non-blocking despite being single-threaded.
The Event Loop: JavaScript’s Asynchronous Engine
Javascript is single threaded (executes one task at a time on its main thread). However it achieves non-blocking behavior through the event loop, allowing it to handle asynchronous tasks like API calls or timers without freezing the application.
The event loop does not execute asynchronous operations itself. Instead, it coordinates between the call stack and the task queues, while the runtime environment (browser or Node.js) handles async operations.
How it works →

- Synchronous code runs on the call stack.
- Async tasks (e.g., setTimeout, fetch) are sent to Web APIs.
- When the Web API completes, callbacks from timers/events go to the task queue (macrotask queue), while promise callbacks go to the microtask queue.
- The event loop checks if the call stack is empty, then moves tasks from the queues to the stack for execution, prioritizing microtasks.
Note - Web APIs (or Node.js APIs) are provided by the runtime environment, not JavaScript itself
Example →
console.log("Start");
setTimeout(() => console.log("Timeout"), 0); // Goes to task queue
Promise.resolve().then(() => console.log("Promise")); // Goes to microtask queue ( more priority )
console.log("End");
// Output: Start, End, Promise, Timeout
Note - In JavaScript, “asynchronous” means deferred execution managed by the event loop, not parallel threads.
Microtask Queue vs Macrotask Queue
Let’s zoom in on two important queues that control when your code runs:
- Microtask Queue (Higher Priority)
These tasks are executed immediately after the current synchronous code, before any macrotasks.
Examples- Promise resolution (then() / catch() / finally()) , queueMicrotask() , MutationObserver callbacks, async/await
- Macrotask Queue (Lower Priority)
These tasks are scheduled to run after the current call stack and all microtasks are done.
Examples: setTimeout, setInterval, setImmediate (Node.js), DOM events (like click, scroll), IO callbacks
Execution Order
The event loop follows this cycle:
- Run all synchronous code
- Run all microtasks until the queue is empty (including newly added microtasks)
- Run one macrotask, then process all microtasks again
- Repeat from step 2
Example -
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("Start");
/* --- Output ---
Start
Promise
Timeout
*/
setTimeout(fn, 0) does not execute immediately—it schedules the callback to run after the current call stack and microtasks are complete.
Note - If microtasks keep adding more microtasks, macrotasks can be delayed (called microtask starvation).
Note 2 - In browsers, rendering (UI updates) typically happens between macrotasks.