This is a classic interview question. What does this print?
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve()
.then(() => console.log('C'))
.then(() => console.log('D'));
queueMicrotask(() => console.log('E'));
(async () => {
console.log('F');
await null;
console.log('G');
})();
console.log('H');If your first thought was "top to bottom, A to H", you are in good company. The code does run top to bottom. But some lines don't run their callback, they schedule it. To predict the order you need three things.
The three pieces
- The call stack. JavaScript has one thread. It runs whatever is on the stack and never interrupts it halfway. Your whole script is the first thing on it.
- The microtask queue.
.then,.catch,.finally, the code afterawaitandqueueMicrotaskput callbacks here. Think of it as "right after the current code". - The macrotask queue.
setTimeout,setInterval, DOM events, network and I/O callbacks put callbacks here. Think of it as "later".
And one rule ties them together:
Run the synchronous code. When the stack is empty, run every microtask, including ones added along the way. Then take one macrotask. Repeat.
Step through it
Type your guess first, then click through. Watch what lands in each queue, and when.
Step through the event loop
Back and Next move one step at a time. The arrow keys work too.
console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve() .then(() => console.log('C')) .then(() => console.log('D')); queueMicrotask(() => console.log('E')); (async () => { console.log('F'); await null; console.log('G');})(); console.log('H');Call stacktop runs now
- script
Microtask queuePromise, await - first
emptyMacrotask queuesetTimeout, events - last
emptyConsole
nothing yetThe script starts. The whole file runs as one task on the call stack, top to bottom. Both queues are empty.
The three things that trip people up
1. An async function starts synchronously
Calling an async function is a normal function call. Everything before the first await runs right away. That's why F comes second, before H. await splits the function in two: the part before it is synchronous, and the part after it behaves like a .then callback.
2. A chained .then is not queued until its parent finishes
.then(D) waits on the promise returned by .then(C). That promise only settles when C has run. So D joins the queue at that moment, at the back, behind E and G, which have been waiting since the script ran. Result: C E G D, not C D E G.
3. setTimeout(fn, 0) does not mean "now"
The delay is a minimum, not a promise. Even with 0, the callback is a macrotask, so it waits for the synchronous code and for every microtask. B comes last.
Final answer: A F H C E G D B.
Practice
Three more scenarios, from easy to sneaky. Guess first, then step through.
Practice: guess, then step through
Pick a scenario. The last one catches a lot of people: a setTimeout after await.
console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve() .then(() => console.log('C')) .then(() => console.log('D')); queueMicrotask(() => console.log('E')); (async () => { console.log('F'); await null; console.log('G');})(); console.log('H');Call stacktop runs now
- script
Microtask queuePromise, await - first
emptyMacrotask queuesetTimeout, events - last
emptyConsole
nothing yetThe script starts. The whole file runs as one task on the call stack, top to bottom. Both queues are empty.
The one-sentence version
If an interviewer asks, this is enough:
JavaScript runs the synchronous code first, then drains the whole microtask queue (promises, await, queueMicrotask), then takes one macrotask (timers, events, I/O), and repeats. A queue fills in the order lines actually execute, not the order they are written.
The last part matters most. In the final practice scenario, the setTimeout sits after an await, so it isn't even registered until the function resumes during the microtask phase. That's why its callback runs after a timer written further down the file.