Prefer reading? The full explanation
How It All Runs Together
The full interview question · Browser
Before this
How await Runs — continuations as microtasks.
After this you can explain
A three-pass method that solves any sync + timer + promise + async ordering question.
console.log('1');
setTimeout(() => console.log('2'), 0);
async function f() {
console.log('3');
await null;
console.log('4');
}
f();
Promise.resolve().then(() => console.log('5'));
console.log('6'); Output: 1 → 3 → 6 → 4 → 5 → 2
The answer, and how to get there in three passes
The output is 1 → 3 → 6 → 4 → 5 → 2. Rather than tracing fourteen lines at once, sort every console.log into three buckets: what runs synchronously, what becomes a microtask, and what becomes a task.
Synchronous pass: 1 prints; the timer is registered; f() is called and its body runs immediately, printing 3, until await null suspends it; the .then callback is registered; 6 prints. Console so far: 1 3 6.
Microtask pass: two are waiting. f()'s continuation was queued on line 7, the .then callback on line 12, so they run in that order and print 4 then 5. Task pass: the timer callback finally prints 2.
The two traps in this snippet
The first trap is line 10. Calling an async function is not scheduling it. The body executes right away on the current call stack, which is why 3 beats 6. Only the part after the await is deferred.
The second trap is await null. There is no promise here at all, so it looks like there is nothing to wait for. The engine still wraps the value, suspends the function, and queues the continuation, which is why 4 cannot print until the synchronous phase is over.
Why 4 comes before 5
Both are microtasks, so neither has priority over the other, and the queue is strictly FIFO. The only question is which was queued first. Line 7 executed before line 12, so f()'s continuation was already waiting when the .then callback arrived.
The experiment below moves the .then above the call to f(). Nothing else changes, and the output becomes 1 → 3 → 6 → 5 → 4 → 2. Position in the source decides the order between microtasks, because position decides which one gets queued first.
A method that survives harder versions
Interviewers extend this pattern by adding nested promises, chained .then() calls, or a second async function. The three-pass method still works: find the synchronous prints, list the microtasks in the order they get queued, and remember that microtasks queued during the drain join the same drain.
The only extra rule worth memorising is that each link of a promise chain costs one additional turn, which is what makes independent chains interleave.
Reading about it is one thing. Watching it run is another.
↑ Run it step by step