JavaScript Event Loop

The JavaScript event loop coordinates synchronous execution with queued asynchronous work. Promise reactions are placed in the microtask queue and, after the current task finishes, microtasks are normally processed before the browser takes another task such as a timer callback.

JavaScript on the browser executes JavaScript code on a single main thread in an ordinary execution context. Synchronous statements run immediately on the call stack until the current task completes. APIs such as timers, events, and network operations can arrange callbacks to run later rather than blocking the current execution.

The browser maintains task queues for work such as timer callbacks and user events. When the call stack becomes empty, the event loop can select a task and run it. A callback scheduled with `setTimeout` therefore does not interrupt currently executing JavaScript, and its delay is a minimum scheduling delay rather than an exact execution time.

Promises use a separate microtask queue. When a promise settles, its `.then()`, `.catch()`, or `.finally()` reaction is scheduled as a microtask rather than being called immediately. After a task finishes, the browser performs a microtask checkpoint and keeps processing newly queued microtasks until the queue is empty.

This ordering explains common output questions: synchronous code runs first, then promise reactions typically run before a timer callback that is waiting in the task queue. A microtask can queue another microtask, so a long or continuously replenished microtask queue can delay other tasks and make the page less responsive.

For frontend interviews, distinguish the JavaScript language's execution model from browser scheduling. The browser decides when tasks, rendering opportunities, and other work are scheduled, while JavaScript execution of a given callback is run to completion before another callback takes over.

Key points

Interview questions

What is the JavaScript event loop?

The event loop is the mechanism that coordinates execution of JavaScript with queued asynchronous callbacks. JavaScript executes the current synchronous work on the call stack without being interrupted by another callback. When that work finishes, the runtime can process queued microtasks and then continue with another task. In browsers, this scheduling works alongside other browser activities such as rendering and user input.

What does this print, and why: `console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); console.log(4);`?

The output is `1`, `4`, `3`, `2`. The first and last synchronous logs execute during the current task, so `1` and `4` appear first. The promise reaction is placed in the microtask queue and is processed after the current task completes. The timer callback is a task, so it runs after the microtask checkpoint.

Why doesn't `setTimeout(fn, 0)` run immediately?

The zero value does not mean that the callback is inserted directly into the current call stack. The timer schedules a task that becomes eligible after the specified delay and other scheduling requirements are satisfied. The currently executing JavaScript must finish before that callback can run. Microtasks that are pending at the relevant checkpoint are also processed before the timer task runs.

What is the difference between a task and a microtask in the browser?

Tasks represent larger units of scheduled browser work, including timer callbacks and many event callbacks. Microtasks include promise reactions and are processed at microtask checkpoints after a task's JavaScript finishes. The runtime drains the microtask queue before moving on to another task. Because microtasks can enqueue more microtasks, an application can delay subsequent tasks if it continuously creates microtask work.

How can excessive microtasks hurt frontend responsiveness?

A microtask checkpoint continues processing until the microtask queue is empty. If each microtask schedules more microtasks, the queue can remain nonempty for a long time. Other tasks, including user input or timers, cannot execute until the relevant JavaScript work yields. Therefore, repeatedly chaining heavy microtasks can monopolize the main thread and contribute to poor responsiveness.