Skip to content
Home JavaScript Event Loop in JavaScript

Event Loop in JavaScript

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Advanced JS → Topic 4 of 27
How the JavaScript event loop works — call stack, Web APIs, callback queue, microtask queue, and why setTimeout(fn, 0) does not run immediately.
🔥 Advanced — solid JavaScript foundation required
In this tutorial, you'll learn
How the JavaScript event loop works — call stack, Web APIs, callback queue, microtask queue, and why setTimeout(fn, 0) does not run immediately.
  • JavaScript is single-threaded — only one function executes at a time in the Call Stack.
  • The Event Loop is a continuous process that monitors the stack and the queues to decide what runs next.
  • The 'Handoff': Synchronous Code > Microtasks (Promises) > Macrotasks (setTimeout/IO).
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

JavaScript is single-threaded but handles async operations through the event loop. The call stack executes synchronous code. When async operations finish (timers, fetch, I/O), their callbacks go into a queue. The event loop moves callbacks from the queue to the call stack only when the stack is empty. Microtasks (Promises) run before macrotasks (setTimeout).

The Call Stack

The call stack is a LIFO (last in, first out) data structure that tracks which function is currently executing. When a function is called, it is pushed on. When it returns, it is popped off. If the stack is busy, nothing else can happen—this is why we say JavaScript is 'blocking' by nature.

Example · JAVASCRIPT
12345678910111213141516171819202122232425
/* 
 * Package: io.thecodeforge.js.core
 */
function multiply(a, b) {
  return a * b;  // [3] pushed then popped
}

function square(n) {
  return multiply(n, n);  // [2] pushed, calls multiply
}

function printSquare(n) {
  const result = square(n);  // [1] pushed, calls square
  console.log(result);
}

printSquare(4);  

// Trace:
// 1. printSquare(4) is pushed
// 2. square(4) is pushed
// 3. multiply(4, 4) is pushed
// 4. multiply returns 16, popped
// 5. square returns 16, popped
// 6. printSquare logs 16, popped
▶ Output
16

Async Operations and the Queue

When you call setTimeout or fetch, JavaScript engine doesn't wait. It hands the work off to the environment's Web APIs (in browsers) or C++ APIs (in Node.js). Your code continues running immediately. When the timer expires or the data returns, the callback is placed in the Macrotask Queue (also known as the Task Queue). It sits there patiently until the Call Stack is completely clear.

Example · JAVASCRIPT
12345678910111213141516
/* 
 * Package: io.thecodeforge.js.async
 */
console.log('1 — start');  

// Handed to Web API timer thread
setTimeout(() => {
  console.log('2 — setTimeout callback'); 
}, 0);

console.log('3 — end');  

// The Event Loop Check:
// 1. Is Stack empty? No (running '3 - end').
// 2. '3 - end' finishes. Stack is empty.
// 3. Event Loop moves callback from Macrotask Queue to Stack.
▶ Output
1 — start
3 — end
2 — setTimeout callback

Microtask Queue — Promises Run First

Not all queues are created equal. JavaScript prioritizes the Microtask Queue (used by Promises and MutationObserver). After the current synchronous task finishes, the Event Loop will drain the entire Microtask Queue before it even looks at the Macrotask Queue. If a microtask schedules another microtask, that new one also runs before the next macrotask (like a setTimeout).

Example · JAVASCRIPT
1234567891011121314151617181920
/* 
 * Package: io.thecodeforge.js.concurrency
 */
console.log('1 — sync start');

setTimeout(() => console.log('2 — setTimeout'), 0);

Promise.resolve()
  .then(() => console.log('3 — Promise .then'));

queueMicrotask(() => console.log('4 — queueMicrotask'));

console.log('5 — sync end');

// Execution Logic:
// [Sync] 1 and 5 log first.
// [Stack Empty] Check Microtasks.
// [Micro] 3 and 4 log.
// [Micro Empty] Check Macrotasks.
// [Macro] 2 logs.
▶ Output
1 — sync start
5 — sync end
3 — Promise .then
4 — queueMicrotask
2 — setTimeout

Why This Matters — Blocking the Event Loop

Because the Event Loop can only move a task to the stack when the stack is empty, a heavy calculation (like finding a large prime number) will 'block' the loop. During this time, the browser cannot render updates, and the UI becomes unresponsive. This is why we offload heavy CPU tasks to Web Workers or break them into asynchronous chunks.

Example · JAVASCRIPT
12345678910111213141516171819202122232425262728293031
/* 
 * Package: io.thecodeforge.js.performance
 */

// BLOCKING VERSION
function block() {
  let i = 0;
  while (i < 1e9) i++; // Heavy sync work
  console.log('Done blocking');
}

// NON-BLOCKING (Chunked) VERSION
function chunkedTask(iterations) {
  let i = 0;
  function doWork() {
    let start = Date.now();
    // Work for only 16ms to maintain 60fps
    while (Date.now() - start < 16 && i < iterations) {
      i++;
    }
    if (i < iterations) {
      setTimeout(doWork, 0); // Yield control back to loop
    } else {
      console.log('Done chunking');
    }
  }
  doWork();
}

chunkedTask(1e9);
console.log('UI stays responsive!');
▶ Output
UI stays responsive!
Done chunking

🎯 Key Takeaways

  • JavaScript is single-threaded — only one function executes at a time in the Call Stack.
  • The Event Loop is a continuous process that monitors the stack and the queues to decide what runs next.
  • The 'Handoff': Synchronous Code > Microtasks (Promises) > Macrotasks (setTimeout/IO).
  • setTimeout(fn, 0) does not mean 'run immediately' — it means 'queue this for the next available tick after the stack is clear'.
  • Blocking the main thread is a cardinal sin in JS; it freezes the entire environment (UI/Node.js server).

Interview Questions on This Topic

  • QPredict the output order of: console.log, setTimeout(0), Promise.resolve().then(), and process.nextTick() (in Node.js).
  • QWhy might a recursive function that uses setTimeout() not cause a 'Maximum call stack size exceeded' error, while a standard recursive function does?
  • QExplain how the 'Starvation' of the macrotask queue can happen if a microtask keeps adding more microtasks to its queue.
  • QIn the context of the Event Loop, why is it usually better to perform heavy calculations in a Web Worker rather than the main thread?
  • QLeetCode Style: Implement a basic 'Task Scheduler' that prioritizes tasks based on whether they are marked as 'urgent' (Microtask) or 'standard' (Macrotask).

Frequently Asked Questions

What is the difference between the microtask queue and the macrotask queue?

Microtasks include Promise callbacks (.then, .catch, .finally) and queueMicrotask. Macrotasks include setTimeout, setInterval, setImmediate (Node.js), and I/O callbacks. The Event Loop will always drain the entire microtask queue completely after every macrotask before moving to the next one in line.

If setTimeout(fn, 0) does not run immediately, when exactly does it run?

It runs after all currently executing synchronous code has finished and the microtask queue has been fully emptied. The '0ms' delay is essentially a request to run the function as soon as possible on the next 'tick' of the event loop.

How does async/await relate to the event loop?

async/await is built on top of Promises. When the engine hits an await keyword, the execution of that specific function is paused, and it yields control back to the main thread. The code following the await is treated like a callback in the microtask queue, which executes once the awaited promise resolves.

Does Node.js have a different event loop than the browser?

While the core concept is identical, Node.js uses the libuv library which has additional phases (Poll, Check, Close callbacks) and unique features like process.nextTick(), which has even higher priority than standard microtasks.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← Previousasync and await in JavaScriptNext →Prototypes and Inheritance in JS
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged