What they're testing
Whether you have a real model of how the runtime schedules work, or just memorised "promises before setTimeout".
The short answer~30 seconds
Synchronous code runs to completion first. Then, with the stack empty, the engine drains the ENTIRE microtask queue — promise callbacks, queueMicrotask, the continuation after an await — including microtasks queued while draining. Only once it's empty does it take ONE macrotask (setTimeout, I/O, a DOM event), then drain microtasks again. So Promise.then always runs before setTimeout(…, 0), even if the timeout was registered first.
The program starts. All synchronous code runs to completion before anything in either queue is touched.
The long answer
A useful framing: a microtask is "work that must finish before the browser is allowed to breathe", a macrotask is "work scheduled for a later turn". Because all microtasks must complete before rendering, an infinite microtask loop freezes the tab outright — no paint, no events. The same loop written with recursive setTimeout leaves the tab responsive, just busy. That's a real consequence, not academic trivia.
await is where surprises live. It doesn't "pause the program": it splits the async function in two, runs the first half immediately and SCHEDULES the rest as a microtask. So awaiting an already-resolved value still costs a microtask turn, and in a few subtle cases the log order differs from calling .then() directly.
A less-known point that interviews well: this ordering isn't in the ECMAScript spec at all. ECMAScript only defines the promise job queue; the task queue, when rendering happens, and setTimeout itself are defined by the HTML spec. Which is why Node.js differs in places — it has process.nextTick cutting in front of microtasks, plus libuv's own phases.
What order does this print?
console.log('1');
setTimeout(() => console.log('2'));
Promise.resolve().then(() => console.log('3'));
queueMicrotask(() => console.log('4'));
(async () => {
console.log('5');
await null;
console.log('6');
})();
console.log('7');1 5 7 3 4 6 2 — synchronous first (1, 5, 7), then microtasks in registration order (3, 4, 6), and only then the macrotask (2).
What they'll ask next
?Does setTimeout(fn, 0) fire after exactly 0ms?
No. The HTML spec clamps to a 4ms minimum after four levels of nesting, and beyond that the callback still waits its turn in the task queue. If a synchronous function runs for two seconds, your timeout waits two seconds.
?How does Node.js differ from the browser?
Node has process.nextTick, which outranks even promise microtasks, and its loop is split into libuv phases (timers, poll, check…), so setImmediate and setTimeout(…, 0) can swap order depending on context. Neither exists in the browser.
?Why does an infinite microtask loop freeze the page when recursive setTimeout doesn't?
Because rendering only happens once the microtask queue is empty. If it never empties, the browser never reaches the paint step. Each setTimeout is its own macrotask, so between turns the browser gets a chance to paint and handle input.
These lose points
- Saying "async makes JavaScript multi-threaded". Async creates no threads; it schedules work on the single one you have.
- Memorising "promises before timeouts" without the why, and getting it wrong the moment nested
awaits appear.
These score well
- Pointing out that rendering sits between macrotasks, so heavy work should be chunked across macrotasks, not microtasks.