Intermediate · Asynchronous JavaScript · topic 40 of 61 · 30 min
The event loop
Explanation
The pieces
- Call stack: the functions currently running. JavaScript runs one thing at a time, top of the stack first.
- Host APIs: the browser (timers, network, DOM events) or Node.js (libuv: files, network, timers). They do the waiting outside your code.
- Macrotask queue (also called the task queue): callbacks of timers, I/O and events wait here.
- Microtask queue: promise reactions (then/catch/finally callbacks, code after await) and queueMicrotask callbacks wait here.
- Event loop: keeps checking: if the stack is empty, run all microtasks, then take the next macrotask, and repeat.
The order rule
- Run the current script (synchronous code) to the end.
- Run EVERY microtask in the queue, including new microtasks added while doing so.
- (In a browser, the page may be repainted here.)
- Take ONE macrotask (for example the oldest ready timer callback) and run it.
- Go back to step 2.
Predicting output
console.log('1 script start');
setTimeout(() => console.log('5 timeout'), 0);
Promise.resolve().then(() => console.log('3 promise 1'))
.then(() => console.log('4 promise 2'));
console.log('2 script end');1 script start
2 script end
3 promise 1
4 promise 2
5 timeoutawait and the microtask queue
async function job() {
console.log('B inside job'); // runs synchronously, up to the first await
await null;
console.log('D after await'); // a microtask
}
console.log('A');
job();
console.log('C');
// A, B inside job, C, D after awaitAn async function runs synchronously until its first await. The rest of the function continues later as a microtask, after the current synchronous code is done.
Why long loops freeze the page
While your code is running, the stack is not empty, so the event loop cannot run anything else: no clicks, no timers, no repainting. A loop that takes 3 seconds freezes the tab for 3 seconds. A setTimeout of 0 ms also waits until the stack is empty, so it can fire much later than asked.
const start = Date.now();
setTimeout(() => console.log('timer after', Date.now() - start, 'ms'), 0);
while (Date.now() - start < 200) {} // busy for 200 ms
// timer after 200 ms (not 0!)Node.js details
Node has the same idea with a few extras: process.nextTick callbacks run even before promise microtasks, and setImmediate runs in a later phase than timers. For most code, "sync first, then microtasks, then macrotasks" is enough.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
The event loop · 1/7
In this lesson you will see how one thread handles many async jobs, using the event loop.
Prefer a video by a teacher? Find free videos on “javascript event loop microtask macrotask explained” on YouTube ↗
In real life: Why the loader never appeared
A developer showed a "Generating report…" message and then ran a 2-second calculation in the same function. Users never saw the message: the browser could not repaint until the stack emptied, and by then the report was done. Moving the heavy work into a setTimeout (or a Web Worker) let the browser paint the message first.
const log = [];
log.push('show loader');
setTimeout(() => { log.push('heavy work'); console.log(log); }, 0);
log.push('function ends; browser can repaint now');Practice (0/3 done)
What is the output order? console.log('a'); setTimeout(() => console.log('b')); Promise.resolve().then(() => console.log('c')); console.log('d');
Fill in the array `order` with the strings "sync", "micro" and "macro" in the order the three callbacks below actually run. Do not change the scheduling code.
Write yieldToEventLoop() that returns a promise resolving on the NEXT macrotask (use setTimeout 0). It is used to let the browser breathe during long work. The test checks that a promise microtask scheduled before it runs first.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- fetch: talking to serversAsynchronous JavaScript · same module
- Promises and the event loopFrontend Web Development · Async JavaScript, modules and tools
- Futures and async/awaitFlutter and Dart · Async Dart and packages
- Node.js: JavaScript on the serverBackend Development · Node.js and Express
- async/await and error handlingFrontend Web Development · Async JavaScript, modules and tools
- async and await: waiting without blockingC# and .NET · Exceptions, delegates, events and async
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which queue do promise then callbacks use?
Answer: Microtask queue
Promise reactions are microtasks.
When does a setTimeout(fn, 0) callback run?
Answer: After the current code and all microtasks
Timers are macrotasks and wait for an empty stack and microtask queue.
async function f() { console.log(1); await 0; console.log(3); } f(); console.log(2); Output?
Answer: 1 2 3
f runs synchronously until await; the rest continues as a microtask after 2.
Why does a 3-second loop freeze a web page?
Answer: The stack never empties, so the event loop cannot handle clicks or repaint
Nothing else can run until the current code finishes.
console.log('A'); setTimeout(() => console.log('B'), 0); console.log('C'); What is printed?Related
Answer: A C B
Even with 0 ms, the timer callback runs after the current code finishes.
What does this print? console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D');Related
Answer: A D C B
Normal code runs first (A, D). Then the microtask queue runs (C), and only then the timer task (B), even with 0 ms.
How do you handle a rejected await?Related
Answer: try/catch around the await
A rejection is thrown at the await, so catch it.
From async and await
What does an async function return?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Asynchronous JavaScript” before its exam: fetch: talking to servers.
- Module 8 exam: Asynchronous JavaScript · 12 questions · 25 min, without help. 6 topics of this module still to finish first.
- Then the next module, TypeScript basics. 61 topics left in the course before the final exam.