What does this event-loop snippet print?
What interviewers are testing
This is a rite-of-passage JavaScript question because it compresses the event loop into four lines. Interviewers watch whether you reason about task queues in order instead of pattern-matching "promises are faster than timeouts". The follow-ups — timers clamping, starvation, rendering order — reveal whether you can apply the model to real asynchronous bugs.
Mental model
The event loop runs one task at a time. A task runs to completion, then the engine drains the entire microtask queue, optionally renders, and only then takes one macrotask. Repeat. Synchronous code is the first part of the current task, so it always prints before any queued callback.
Step-by-step solution
Step 1 of 5
Predict the printed order
Before running the snippet, say the order out loud. Most candidates answer A D B C, assuming promises and timers queue together, or A B C D, assuming a zero-millisecond timer fires immediately. The correct output is A D C B. The reasoning is mechanical, not mysterious. console.log('A') and console.log('D') are synchronous statements in the script's own task, so they run to completion immediately. setTimeout registers B in the macrotask queue and Promise.then registers C in the microtask queue, but neither callback can interrupt the running task. Watch the animation: A and D light up on the call-stack lane before anything moves on the microtask lane, and B waits even longer on the macrotask lane. Narrating this order in advance — sync, then all microtasks, then one macrotask — is exactly what the interviewer is listening for.
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');Animation — Predict the printed order
The script task starts: line 1 prints A synchronously.
Edge cases & traps
- Assuming setTimeout(fn, 0) is immediate: browsers clamp nested timers to about 4 ms, so use queueMicrotask or MessageChannel when you truly need to yield immediately.
- Forgetting that code after await is a microtask: an async continuation still runs before any timer callback.
- Treating Node and browsers as identical: process.nextTick beats promise microtasks in Node, and setImmediate runs in the check phase, not the timer phase.
- Starving the loop with a promise chain that reschedules itself: insert a macrotask yield so rendering and timers can run.
- Calling requestAnimationFrame a microtask: rAF is a render-phase callback that fires before the next paint, not a microtask; relative to a timer it fires at the next render opportunity — never between synchronous code and the microtask queue.