LESSON 25 · INTERMEDIATE4 min read

The Event Loop, Microtask Queue & Execution Phases

Step inside the asynchronous heartbeat of JavaScript. Trace execution order across the Call Stack, Microtask Queue, and Task Queue (Macrotasks).

ES2024 / Node.js 20+Free to readModule 7 of 10
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Visualize the event loop coordinator moving tasks between queues and the single call stack.
  • Differentiate between Microtasks (Promises, queueMicrotask, MutationObserver) and Tasks (setTimeout, setInterval, I/O).
  • Predict precise execution order across complex synchronous and asynchronous code blocks.

01The Single-Threaded Illusion: How Asynchronous JS Works

JavaScript is single-threaded: the engine has only one call stack and can only execute one line of JavaScript at any single instant. How then does a browser or Node.js runtime handle network requests, mouse clicks, database queries, and timers without freezing the entire application?

The answer lies in the host environment and the Event Loop. While V8 executes your JavaScript code, the host environment (the browser or Node.js C++ runtime) provides Web APIs and background threads. When you invoke setTimeout or fetch(), the engine hands the operation off to the host environment and immediately continues executing synchronous code on the call stack.

example.js⚡JavaScript
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02The Two Queues: Microtasks vs Tasks (Macrotasks)

When asynchronous work completes in the host environment, its callback does NOT immediately interrupt the call stack. Instead, callbacks wait in queues. Crucially, there are two distinct queues with strict priority hierarchy: the Microtask Queue and the Task Queue (formerly Macrotask Queue).

Tasks include setTimeout, setInterval, setImmediate, and I/O events. Microtasks include Promise resolution handlers (.then, .catch, .finally), queueMicrotask(), and DOM MutationObserver callbacks.

The golden rule of the event loop is: After the current synchronous call stack empties, the engine MUST drain the ENTIRE Microtask Queue before it picks a single task from the Task Queue. If a microtask schedules another microtask, that new microtask runs in the same tick!

example.js⚡JavaScript
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03The Rendering Loop and Microtask Starvation

In web browsers, the event loop also coordinates UI rendering (style calculation, layout, painting), which typically occurs at 60Hz or 120Hz (every 16.6ms or 8.3ms). Rendering can only take place when the call stack is completely idle.

Because the engine drains all microtasks before checking the rendering pipeline or picking the next task, creating an infinite loop of microtasks (e.g. recursive Promise.resolve().then()) completely starves the event loop. The UI freezes, user clicks stop responding, and the browser tab crashes.

example.js⚡JavaScript
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04Why This Matters in Production

Diagnosing production latency and UI jank requires precise mental modeling of the event loop. When breaking up long-running computational tasks (such as parsing huge JSON files or rendering virtual tables), developers often mistakenly use Promise.resolve().then() to yield control.

Because microtasks drain immediately before rendering, this does NOT yield to the UI renderer. To allow the browser to paint a frame or process a user click, you must yield to the Task Queue using setTimeout(fn, 0) or the modern scheduler.yield() Web API.

LET IT CLICK

The ideas worth keeping.

  • Synchronous code runs first, microtasks run second (all of them), and macrotasks run third.
  • Promises and queueMicrotask schedule microtasks; setTimeout and setInterval schedule macrotasks.
  • To yield control to the browser's rendering engine during heavy processing, use macrotask delays (setTimeout or scheduler.yield).
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Event Loop Trace Challenge

Write a function that schedules one synchronous log, one microtask with queueMicrotask, and one task with setTimeout, returning an array verifying their execution sequence.

your_turn_1.js⚡JavaScript
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.jsJavaScript
function verifyExecutionOrder() {  const log = [];  log.push('sync');  setTimeout(() => log.push('task'), 0);  queueMicrotask(() => log.push('microtask'));  return log; // At this instant, ['sync']}

Synchronous code pushes 'sync' first. After call stack empties, microtask runs next, followed finally by the timer task.

02

Exercise 2: Yielding to the Browser Renderer

Implement a utility yieldToMain() returning a Promise that resolves on the Task Queue using setTimeout(resolve, 0), allowing UI renders to occur.

your_turn_2.js⚡JavaScript
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.jsJavaScript
function yieldToMain() {  return new Promise(resolve => setTimeout(resolve, 0));}

Wrapping setTimeout in a Promise pushes the resumption of an async function onto the Task Queue, giving the browser opportunity to recalculate styles and paint.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01When does the event loop process tasks waiting in the Microtask Queue?
02Which of the following creates a Microtask?
03What happens if an asynchronous function recursively enqueues microtasks without terminating?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Event Loop Trace Challenge

Write a function that schedules one synchronous log, one microtask with queueMicrotask, and one task with setTimeout, returning an array verifying their execution sequence.

EXPECTED OUTPUT
[Process exited with code 0 - No output]
main.jsJavaScript
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.