Skip to main content
Web DevelopmentSoftware Engineering

Understanding JavaScript Event Loop: Beyond the Basics

A deep dive into the JavaScript event loop, microtasks, macrotasks, and how they affect your application's performance.

Mbeah Essilfie

Mbeah Essilfie

July 15, 2026 at 06:02 AM

11 min read 1033 views
Understanding JavaScript Event Loop: Beyond the Basics

The Mental Model

JavaScript is single-threaded, but that doesn't mean it can only do one thing. The event loop is the mechanism that makes asynchronous operations possible without threads.

The Execution Order

console.log('1: Synchronous')

setTimeout(() => console.log('2: Macrotask'), 0)

Promise.resolve().then(() => console.log('3: Microtask'))

queueMicrotask(() => console.log('4: Microtask via queueMicrotask'))

console.log('5: Synchronous')

Output:

1: Synchronous
5: Synchronous
3: Microtask
4: Microtask via queueMicrotask
2: Macrotask
Microtasks (Promises, queueMicrotask) always execute before the next macrotask (setTimeout, setInterval, I/O). This is why a recursive microtask can starve the event loop.

The Full Picture

  1. Call Stack — Executes synchronous code
  2. Microtask Queue — Promises, MutationObserver, queueMicrotask
  3. Macrotask Queue — setTimeout, setInterval, I/O, UI rendering
  4. Animation Frames — requestAnimationFrame callbacks

After each macrotask completes, the engine drains all microtasks before moving to the next macrotask or rendering.

Real-World Impact: Chunking Heavy Work

// BAD: Blocks the main thread
function processItems(items: Item[]) {
  items.forEach(item => heavyComputation(item))
}

// GOOD: Yields to the event loop between chunks
async function processItemsChunked(items: Item[], chunkSize = 50) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize)
    chunk.forEach(item => heavyComputation(item))

    // Yield to allow UI updates and other tasks
    await new Promise(resolve => setTimeout(resolve, 0))
  }
}

Node.js Event Loop Phases

In Node.js, the event loop has additional phases:

  1. Timers — setTimeout/setInterval callbacks
  2. Pending callbacks — I/O callbacks deferred from previous cycle
  3. Idle/Prepare — Internal use
  4. Poll — Retrieve new I/O events
  5. Check — setImmediate callbacks
  6. Close — Socket close events
// setImmediate vs setTimeout in Node.js
setImmediate(() => console.log('immediate'))
setTimeout(() => console.log('timeout'), 0)
// Order is non-deterministic at top level!

// But inside an I/O callback, setImmediate always fires first:
const fs = require('fs')
fs.readFile(__filename, () => {
  setImmediate(() => console.log('immediate'))  // Always first
  setTimeout(() => console.log('timeout'), 0)
})

Key Takeaways

  • The event loop is not just "callbacks run later"
  • Microtasks have priority over macrotasks
  • Long synchronous code blocks everything — break it up
  • Understanding phases helps debug timing issues in Node.js
JavaScriptNode.jsPerformance
Mbeah Essilfie

Written by Mbeah Essilfie

Fullstack Software Developer

Read next

The Complete Guide to Vue 3 Composables
12 min read

The Complete Guide to Vue 3 Composables

Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.

Mbeah EssilfieMbeah Essilfie
981