What is the JavaScript event loop?

The event loop is how JavaScript handles asynchronous code - it picks callbacks from a queue and runs them one at a time on a single thread.

JavaScriptAsync

Overview

JavaScript is single-threaded - only one piece of code runs at a time. The event loop is the mechanism that lets JavaScript handle async operations (timers, network requests, user events) without blocking. When async work completes, its callback is placed in a queue; the event loop picks it up once the call stack is empty.

Key Points

  • β€’Call stack: where synchronous code executes. Only one frame runs at a time.
  • β€’Web APIs (browser) / Node.js APIs: handle async work (setTimeout, fetch, fs) off the main thread.
  • β€’Task queue (macrotask): holds callbacks from `setTimeout`, `setInterval`, `I/O`.
  • β€’Microtask queue: holds Promise `.then` callbacks and `queueMicrotask`. Runs before the next macrotask.
  • β€’Order: call stack empties β†’ all microtasks run β†’ one macrotask runs β†’ repeat.

Examples

Execution order - the classic puzzle

javascript
console.log('1 - synchronous');

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

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

console.log('4 - synchronous');

// Output:
// 1 - synchronous
// 4 - synchronous
// 3 - Promise microtask     ← microtasks run before macrotasks
// 2 - setTimeout (macrotask)

Even with a 0ms delay, setTimeout runs after all microtasks.

Why blocking the main thread is bad

javascript
// This blocks the thread for 3 seconds - UI freezes, no events processed
const end = Date.now() + 3000;
while (Date.now() < end) { /* busy-wait */ }

// Instead, use setTimeout or async APIs that don't block the event loop

Microtasks vs macrotasks with async/await

javascript
async function run() {
  console.log('A');
  await Promise.resolve(); // pauses here, queues a microtask
  console.log('B');        // runs in the microtask queue
}

run();
console.log('C');

// Output: A β†’ C β†’ B

Pro Tip

The practical takeaway: Promises (and `await`) resolve before `setTimeout`, even a 0ms timeout. This is because Promises use the microtask queue which always drains before the next macrotask. This matters for test ordering and sequencing async logic.