Skip to main content

Overview

The event loop is the heart of JavaScript’s concurrency model. It enables non-blocking I/O and asynchronous programming despite JavaScript being single-threaded. Understanding event loop mechanics is essential for writing performant, responsive applications.
The event loop continuously checks for work to do: executing scripts, processing events, and running queued callbacks. All JavaScript execution happens on a single thread, but the event loop orchestrates when different types of work get processed.

Event loop architecture

The event loop manages multiple task queues with different priorities and processing rules.

Processing phases

1

Execute current task

Run JavaScript code until the call stack is empty (script execution, event handler, or callback)
2

Process all microtasks

Drain the microtask queue completely, including any new microtasks added during processing
3

Render update (browser only)

If rendering is needed, run animation frame callbacks and update the display
4

Process next macrotask

Take one macrotask from the queue and execute it
5

Repeat

Go back to step 2 and continue the loop
Microtasks always run before the next macrotask or render. A microtask that continuously queues new microtasks will starve macrotasks and freeze the UI.

Microtask queue

Microtasks are high-priority tasks that execute immediately after the current task completes, before yielding to the browser.

What creates microtasks

Key behaviors:
  • .then(), .catch(), .finally() schedule microtasks
  • Microtasks run in order they were queued
  • New microtasks execute before exiting microtask phase

Microtask queue draining

The microtask queue is fully drained before moving to the next phase:
The nested microtask at step 6 runs before the macrotask because the entire microtask queue must drain before processing macrotasks.

Macrotask queue

Macrotasks (also called tasks) are lower-priority work items that execute one at a time, with microtask processing and rendering between each.

What creates macrotasks

Timing guarantees:
  • Minimum delay, not exact timing
  • Browser may clamp to 4ms for nested calls
  • Background tabs may be throttled to 1000ms

Macrotask processing

Unlike microtasks, only one macrotask runs per event loop iteration:
Long-running macrotasks block rendering and user input. Break work into smaller chunks using setTimeout or requestIdleCallback.

Animation frame callbacks

Animation frame callbacks run before rendering, synchronized with the display refresh rate (typically 60Hz).

requestAnimationFrame timing

Render timing and optimization

requestAnimationFrame automatically:
  • Synchronizes with display refresh (60fps or higher)
  • Pauses in background tabs to save resources
  • Batches DOM reads/writes to avoid layout thrashing

Render pipeline

The browser may skip rendering frames if the previous frame took too long, dropping to 30fps or lower to maintain responsiveness.

Idle callbacks and scheduling

Idle callbacks run during idle periods when the browser has completed high-priority work.

requestIdleCallback

deadline.timeRemaining():
  • Returns milliseconds until frame deadline
  • Typically 16.6ms - (work done so far)
  • May be 0 if frame is already late

Task prioritization

Modern browsers support task prioritization through the Scheduler API and manual chunking strategies.

Scheduler API (experimental)

Long task detection

Tasks over 50ms block user input and cause jank. Break long tasks into 50ms chunks to maintain 60fps responsiveness.

Complete event loop example

Here’s a comprehensive example demonstrating all queue types:

Best practices

Next steps

Garbage collection

Understand memory management and GC optimization strategies

JIT optimization

Learn how engines optimize hot code with just-in-time compilation