Compile Ready
Module 8 · Asynchronous JavaScript

The Event Loop In Depth

Advanced14m read18m practice32m total
Event LoopCall StackMicrotasksMacrotasksOutput Prediction

Introduction

The event loop is the runtime mechanism that lets single-threaded JavaScript coordinate synchronous code, timers, browser or host APIs, promises, and UI work without blocking the whole environment.

For interviews, this is the highest-yield async topic. If you can accurately trace the call stack, Web APIs, the task queue, and the microtask queue, you can solve almost every setTimeout + Promise.then output puzzle.

Why This Matters

Senior interviewers use event-loop questions to test whether you reason from first principles or memorize snippets. The most important rule is simple but unforgiving: after the current synchronous work finishes, the runtime drains all microtasks before it runs the next task/macrotask. Missing that rule is the reason most wrong answers put setTimeout(..., 0) before promise callbacks.

Theory

The parts of the model

JavaScript execution is run-to-completion: once a function starts running on the call stack, nothing else interrupts it until the stack becomes empty. Async behavior comes from the runtime around the engine.

  • Call stack: where currently executing function frames live. Synchronous code enters and leaves this stack.
  • Host APIs / Web APIs: capabilities supplied by the environment, such as timers, DOM events, network work, and message channels. They are not all part of the ECMAScript language.
  • Task queue, often called the macrotask queue: callbacks from timers, UI events, message events, and similar task sources wait here.
  • Microtask queue: promise reactions (then, catch, finally), queueMicrotask, and a few host features wait here.

One event-loop turn

A useful interview algorithm is:

  1. Run the currently selected task. The initial script itself is a task.
  2. Keep executing synchronous calls until the call stack is empty.
  3. Drain the entire microtask queue. If a microtask schedules another microtask, it is appended and must also run before the loop moves on.
  4. In browsers, rendering may happen after microtasks drain.
  5. Pick the next task from a task queue and repeat.

The key ordering rule

setTimeout(fn, 0) does not mean "run immediately". It means "after at least this delay, enqueue fn as a future task". Promise callbacks are microtasks, so they run after the current stack clears but before that future timer task.

Starvation

Because the runtime drains all microtasks before the next task, recursively scheduling microtasks can starve timers, user input, and rendering. That is why high-volume scheduling sometimes deliberately yields with a task, such as a timer, rather than chaining only promises.

Browser versus Node nuance

Node.js has its own event-loop phases and an extra process.nextTick queue, but the premium interview mental model remains: synchronous code first, promise microtasks next, timer or I/O tasks later. This course focuses on browser/Web Worker compatible behavior unless a Node-specific example is explicitly marked.

Visual Diagrams

Event loop interview mental model

Initial script task
        |
        v
+------------------+       async request        +------------------+
|   Call stack     | -------------------------> |    Web APIs      |
| function frames  |                            | timers, events   |
+------------------+                            +------------------+
        |                                                  |
        | stack empty                                      | callback ready
        v                                                  v
+------------------+                            +------------------+
| Microtask queue  | <--- promises,             |   Task queue     |
| drain all of it  |      queueMicrotask        | timers, events   |
+------------------+                            +------------------+
        |                                                  ^
        | all microtasks done                              |
        +---------------- event loop picks next task -------+

The event loop runs a task, drains every microtask, then moves to the next task.

One complete loop turn

[Task starts]
   |
   v
Run synchronous JavaScript on the call stack
   |
   v
Call stack empty?
   |
   v
Drain microtask queue until empty
   |
   v
Browser may render
   |
   v
Pick the next task

Microtasks are not one-per-turn; the entire microtask queue drains before the next task.

Code Examples

Classic event-loop ordering

The timer callback is a future task. The promise callback is a microtask, so it runs first after synchronous code completes.

Loading…

A browser event is another task source

DOM event callbacks are queued as tasks by the browser. Promise work scheduled inside an event handler drains before the browser moves to the next task.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1
2console.log('A');
3
4setTimeout(function () {
5 console.log('B');
6}, 0);
7
8Promise.resolve().then(function () {
9 console.log('C');
10});
11
12console.log('D');

Predict the output #2

javascript
1
2console.log('start');
3
4setTimeout(function () {
5 console.log('timer 1');
6
7 Promise.resolve().then(function () {
8 console.log('microtask inside timer');
9 });
10}, 0);
11
12setTimeout(function () {
13 console.log('timer 2');
14}, 0);
15
16Promise.resolve().then(function () {
17 console.log('promise 1');
18});
19
20console.log('end');

Coding Exercises

Build an ordering tracer

Medium

Implement traceEventLoop() so it logs sync start, sync end, microtask 1, microtask 2, and task in exactly that order. Use one timer and two microtasks.

Interview Questions

1Explain the event loop using call stack, Web APIs, task queue, and microtask queue.

The engine runs synchronous JavaScript on the call stack. Host APIs handle async work such as timers or events and enqueue callbacks later. Timer and event callbacks enter the task queue. Promise reactions and queueMicrotask callbacks enter the microtask queue. After each task finishes and the call stack is empty, the event loop drains all microtasks before selecting the next task.

Asked at:GoogleMetaMicrosoft

Follow-ups

  • Why does a promise callback run before `setTimeout(..., 0)`?
  • What happens if a microtask schedules another microtask?
2What does `setTimeout(fn, 0)` guarantee?

It guarantees only that fn will not run until the current call stack has cleared and the timer is eligible. It does not guarantee immediate execution. Promise microtasks already queued for the current turn run before that timer task, and other tasks may also affect when the timer is picked.

Quiz

1. After the current call stack becomes empty, what does the event loop do before running the next timer task?

2. Which callback normally runs first after synchronous code: `Promise.resolve().then(fn)` or `setTimeout(fn, 0)`?

Summary

  • JavaScript runs synchronous code to completion on the call stack.
  • Host APIs enqueue future callbacks into task queues when async work is ready.
  • Promise reactions and `queueMicrotask` callbacks are microtasks.
  • After each task, the runtime drains all microtasks before running the next task.

Cheat Sheet

Ordering: sync code → all microtasks → next task/macrotask.

Task examples: initial script, timers, DOM events, message events.

Microtask examples: Promise.then, catch, finally, queueMicrotask.

Interview rule: setTimeout(..., 0) is never before already queued promise callbacks.