JavaScript's Event Loop: Macrotasks & Microtasks

The JavaScript event loop processes tasks like `setTimeout` callbacks or user clicks from a macrotask queue. A single, long-running macrotask blocks all rendering and user input, freezing the UI. The footgun is assuming `setTimeout(fn, 0)` runs instantly.
WHY IT EXISTS JavaScript runs on a single thread, meaning it can only do one thing at a time. To handle concurrent operations like user input, network requests, and timers without blocking, it uses an event loop to manage a queue of tasks. This system allows a single thread to remain responsive by processing events and executing code in an orderly, non-blocking fashion.
THE MENTAL MODEL Think of the event loop as a manager with a to-do list called the macrotask queue. It picks the oldest task, works on it until it's completely finished, and only then looks at the list again. While it's working on a task, it's completely deaf to new user events or timers. The browser can't even repaint the screen. This is why a single long task freezes the page.
HOW IT WORKS The JS engine runs an endless loop. When the main call stack is empty, it checks the macrotask queue. If a task is present (e.g., a setTimeout callback is due, a user clicked a button), it pulls that task off the queue and executes it. Any new macrotasks that arrive while one is running are added to the end of the queue. There is also a separate, higher-priority microtask queue (for things like Promise resolutions), but macrotasks are the primary mechanism for handling events, scripts, and rendering.
WHEN TO USE IT You actively manage the macrotask queue when you have a CPU-intensive operation that would block the main thread. For example, processing a large dataset, performing complex calculations, or syntax-highlighting a large block of code. By breaking the task into smaller pieces and scheduling each piece with setTimeout(fn, 0), you yield control back to the event loop periodically, allowing the UI to stay responsive.
WHEN NOT TO USE IT Don't split trivial, fast operations into macrotasks. The overhead of setTimeout (creating a timer, queueing a task) would make the code slower and more complex for no benefit. This technique is specifically for long-running, synchronous code that would otherwise cause the UI to become unresponsive. It is a solution for unblocking the main thread, not a general-purpose tool for all functions.
ONE CANONICAL EXAMPLE A function needs to count from 1 to 1,000,000,000. A simple for loop will execute as a single, massive macrotask, freezing the browser for seconds and potentially triggering a "Page Unresponsive" warning. The correct approach is to split the work. A function can count 1,000,000 times, then use setTimeout(count, 0) to schedule the next million counts. This creates 1,000 separate macrotasks. Between each one, the browser is free to process user input and render updates, keeping the UI alive.
Read the original → javascript.info
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.