Finish synchronous execution, then drain the microtask queue—including microtasks added while it drains. A timer callback cannot interrupt that work. Track enqueue order instead of memorizing a priority list of API names.
Exercise 1: a promise, a nested microtask, and a timer
Run this as a single script in a fresh browser page, or paste the whole block into the console. Before executing it, write six output lines and a separate list of pending microtasks. setTimeout schedules a later task; it does not make its callback run immediately just because the delay is zero.
The exact output is: start → end → promise → microtask → nested → timer. The first promise callback and the explicit microtask are already queued when synchronous code finishes. The nested callback joins the tail only when the promise callback executes.
console.log('start');
setTimeout(() => console.log('timer'), 0);
Promise.resolve().then(() => {
console.log('promise');
queueMicrotask(() => console.log('nested'));
});
queueMicrotask(() => console.log('microtask'));
console.log('end');Trace the queue after each scheduling step
Treat this table as a debugging worksheet. The running callback is no longer in the pending queue. When promise executes, microtask is already waiting, so nested goes behind it. There is no general rule that every promise callback outranks every queueMicrotask callback.
The HTML event-loop model has task queues and rendering opportunities. This small example has one timer and no competing event sources, so its output is deterministic. It does not establish a universal ordering between unrelated network, input, and timer events, or guarantee a paint after each callback.
| After | Pending microtasks | Output so far |
|---|---|---|
| Synchronous script | promise, microtask | start, end |
| promise callback | microtask, nested | start, end, promise |
| microtask callback | nested | start, end, promise, microtask |
| nested callback | empty | start, end, promise, microtask, nested |
| timer callback | empty | start, end, promise, microtask, nested, timer |
Exercise 2: does await 0 suspend the function?
Yes. Calling an async function starts its body synchronously, but reaching await suspends that function's continuation even when the value is not a pending promise. The caller continues running. In this example, the continuation is queued before the later queueMicrotask call.
The output is script:start → review:start → script:end → review:resume → queued. Move queueMicrotask above review() and predict the last two lines again: queued now comes before review:resume. That one-line change tests whether you understand scheduling order rather than the original answer.
async function review() {
console.log('review:start');
await 0;
console.log('review:resume');
}
console.log('script:start');
review();
queueMicrotask(() => console.log('queued'));
console.log('script:end');Give an interview answer that survives a follow-up
Start by naming the execution environment, then walk through the synchronous calls. Mark where each callback is enqueued, and distinguish calling a function from scheduling its continuation. Finally, show the queue after every callback that schedules more work. This gives the interviewer a checkable explanation even if you make a small transcription mistake.
A useful follow-up is to replace the nested microtask with another timer. In that version, nested is no longer part of the same microtask checkpoint. Another is to ask what happens when a microtask keeps adding itself: it can delay subsequent tasks and rendering indefinitely. Do not run an unbounded version in your working browser tab.
- An async function's code before its first await still runs synchronously.
- A zero-delay timer means eligible later, not execute now or exactly zero milliseconds later.
- Promise callbacks and queueMicrotask callbacks follow their enqueue order in these examples.
- Rendering is scheduled by the browser; this trace is not a frame-rate model.
Turn the trace into a short review session
Save the code without its answer. On the next review, reconstruct the output and explain the two moments that change the queue: entering the promise callback and reaching await. Then make one change and predict the new output before running it. A flashcard that only asks for six memorized labels misses the mechanism.
Use the JavaScript question set for broader follow-ups about promises and the runtime. RecallDeck adds scheduled recall of interview concepts; keep the actual code execution in your editor or browser alongside that practice.
Quick answers
Frequently asked questions
Do promises always run before setTimeout?
In the first exercise, the already-scheduled promise reaction runs before the timer callback. A promise that settles later is a different case: a timer may execute before that promise has any reaction ready to run.
Is queueMicrotask faster than Promise.then?
This exercise explains ordering, not a performance benchmark. Both schedule microtask work here; choose the API for its semantics, including how errors are reported.
Can I apply the same answer to Node.js?
These examples also use APIs available in Node.js, but do not generalize the trace to process.nextTick, setImmediate, I/O phases, or module-context differences without examining Node's scheduling rules.
Source notes
References and review policy
Information checked on October 21, 2026. Section links identify sources for factual claims and technical explanations. Interpretations, practice scenarios and preparation recommendations are RecallDeck’s editorial work.
From reading to recall
Practice the full interview loop.
RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.