Output Quiz #3 — The Event Loop & Task Ordering

10 output questions on setTimeout(0) vs Promise, microtask vs macrotask order, async/await interleaving, and queueMicrotask — predict the exact execution order.

10 min read
JavaScript
Interview
Output

TABLE OF CONTENTS
Output Quiz #3 — The Event Loop & Task Ordering

The event loop is the most counter-intuitive topic in JS interviews because the output depends on understanding three separate queues: the call stack, the microtask queue, and the task queue. Ten questions that will sharpen your mental model of execution order.


Q1 — setTimeout(0) vs synchronous code

Loading editor...

Show answer

Output:

A
C
B

Why: console.log("A") and console.log("C") run synchronously on the call stack. setTimeout schedules "B" as a macrotask — it goes into the task queue and only runs after the current synchronous execution is complete. Even with a delay of 0, the callback never jumps the queue ahead of synchronous code.


Q2 — Promise vs setTimeout

Loading editor...

Show answer

Output:

start
end
promise
timeout

Why: Execution order: synchronous code first (start, end), then the microtask queue is drained (.then callbacks), then the task queue runs (setTimeout callbacks). Promise .then callbacks are microtasks — they always run before the next macrotask. setTimeout is a macrotask, so it waits even though its delay is 0.


Q3 — Chained .then()

Loading editor...

Show answer

Output:

sync
1
2
3

Why: console.log("sync") runs first (synchronous). Then the microtask queue is processed: the first .then runs (logs 1, returns 2), which schedules the second .then as a new microtask (logs 2, returns 3), which schedules the third (logs 3). Each .then callback is enqueued only after the previous one completes.


Q4 — queueMicrotask vs setTimeout

Loading editor...

Show answer

Output:

sync
micro
macro

Why: queueMicrotask explicitly enqueues a microtask — same queue as Promise.then. The microtask queue is drained completely after each task, before the next macrotask runs. So order is: synchronous → microtasks → macrotasks.


Q5 — async/await interleaving

Loading editor...

Show answer

Output:

before
foo start
after
foo after await

Why: foo() is called synchronously. Inside foo, "foo start" logs immediately. Then await Promise.resolve() suspends foo — it schedules the rest of the function ("foo after await") as a microtask and returns control to the caller. "after" logs synchronously. Then the microtask runs: "foo after await".


Q6 — Multiple awaits

Loading editor...

Show answer

Output:

1
4
2
3

Why: run() executes synchronously until the first await null. At that point it suspends, returning control: 4 logs. Then the microtask for the resumed run() fires: 2 logs. The second await null suspends again, but there's no more synchronous code, so the next microtask fires immediately: 3 logs.


Q7 — Promise constructor is synchronous

Loading editor...

Show answer

Output:

A
B
D
C

Why: The Promise executor function ((resolve) => { ... }) runs synchronously when the Promise is constructed. So "B" logs immediately. The .then callback ("C") is a microtask — it runs after all synchronous code ("D") completes.


Q8 — Nested setTimeout

Loading editor...

Show answer

Output:

1
3
4
2

Why: Both outer setTimeouts are queued as macrotasks. The first runs: logs 1, schedules a new macrotask for 2, logs 3. Then the second outer setTimeout runs (it was queued before the inner one): logs 4. Finally the innermost setTimeout runs: logs 2. Nested setTimeout pushes to the back of the task queue, behind already-scheduled tasks.


Q9 — Promise then vs async/await mixing

Loading editor...

Show answer

Output:

start
a1
b1
end
a2

Why: a() starts synchronously: "a1" logs. await b() calls b() synchronously: "b1" logs. b() returns a resolved promise. The await suspends a() (schedules "a2" as a microtask) and returns control to a()'s caller. "end" logs. Then the microtask runs: "a2".


Q10 — Microtask starvation

Loading editor...

Show answer

Output:

sync done
(hangs — "setTimeout ran" never logs in a real browser)

Why: Each microtask schedules another microtask before the engine can drain the queue. The microtask queue is fully drained before any macrotask runs — so the setTimeout callback never gets a turn. This is microtask starvation: an infinite chain of microtasks prevents all macrotasks (rendering, I/O, timers) from running. In a real browser, this freezes the page.


Key Rules

QueueWhat goes thereWhen it runs
Call stackSynchronous codeRight now
Microtask queuePromise.then, queueMicrotask, await resumeAfter every task, before next macrotask
Task queue (macrotask)setTimeout, setInterval, I/O, eventsOne at a time, after microtask queue is empty

The golden rule: synchronous → all microtasks → one macrotask → all microtasks → one macrotask → …


Go Deeper


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI