36 verified snippets
Grouped by topic — stack basics, setTimeout, .then chains, queueMicrotask, async/await, interview classics — difficulty-tagged and hash-linkable.
Async ordering is not magic; it is one small loop with strict rules. This page animates those rules on real snippets — and every console order you will see here is proven by running the code in real Node, not asserted from memory. It is the guided tour behind looptrace, the step-through visualizer.
01 · The trap
This is the most evergreen JavaScript interview question there is — and a daily debugging trap. The naive guess is A B C D. Node prints something else.
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
✓ Order verified by executing this exact code in Node v25.4.0 on 2026-07-23 — re-proven by this site's test suite on every build.
Why? setTimeout queues a macrotask; .then queues a microtask — and microtasks always run first. The next three scenes show exactly why.
02 · One turn of the loop
The same four lines, played across the loop's lanes — exactly how looptrace's step player draws it. Crimson always marks the thing executing right now.
empty — turn complete
drained
drained
1 · run script: script runs on the stack — logs A, queues the timeout (macrotask), queues the .then (microtask), logs D, then pops.
2 · microtask checkpoint: before anything else, the microtask queue is drained — the .then callback runs and logs C.
3 · next macrotask: only now is the timeout callback taken — it runs and logs B. The tick dot is back where it started.
03 · The drain rule
Two timeouts are queued first, yet both promise callbacks still beat them — because a microtask queued during the checkpoint is drained in the same checkpoint.
setTimeout(() => console.log('t1'), 0);
setTimeout(() => console.log('t2'), 0);
Promise.resolve().then(() => {
console.log('p1');
Promise.resolve().then(() => console.log('p2'));
});
Rule: the checkpoint empties the whole microtask queue before the loop may take one — just one — macrotask.
04 · await, demystified
await suspends the function — it never blocks the threadAt await, the function pauses and its continuation is queued as one microtask. Everything after the call keeps running first.
async function f() {
console.log('1');
await null;
console.log('2');
}
f();
console.log('3');
f() is called and runs synchronously — logs 1await null suspends f; its continuation is queued as one microtask3, then popsf — logs 2Modern semantics: since ES2019, an await on a resolved value costs exactly one microtask tick. looptrace models these modern semantics and says so.
05 · Proven, not asserted
Most event-loop diagrams show the order the author believes is right. looptrace — and this page — are different by construction.
All 36 looptrace snippets pass this gate, verified against Node v25.4.0 on 2026-07-23 — and this explainer runs the same proof on its own three demos in test/facts.test.js, so the page can never drift from reality.
06 · Enforced privacy
connect-src 'none'Privacy here is not a promise in a policy page; it is a Content-Security-Policy rule the browser enforces. This page — and looptrace — cannot make a network request.
fetch, XHR and WebSockets are all refused by the CSP.07 · Inside looptrace
Grouped by topic — stack basics, setTimeout, .then chains, queueMicrotask, async/await, interview classics — difficulty-tagged and hash-linkable.
Prev / next / autoplay / arrow keys over call stack, microtask queue, macrotask queue and console — with a plain-English caption for every single step.
A crimson tick dot advances around the loop — run script → drain microtasks → next macrotask — so you always know which phase you are in.
Arrange the expected console lines before playback, then reveal. Your correct-streak lives only in localStorage. The interview-prep hook.
Each snippet names the WHATWG / MDN / V8 rule it demonstrates, plus the exact Node version and date its console order was verified against.
Copy any annotated step trace as Markdown or print it. Node-only phases (process.nextTick, setImmediate) are explained in prose — never simulated.
Reading about the loop is one thing. Stepping a real snippet through the stack and queues — and predicting the order before you press play — is how it sticks.
Open looptrace — 36 proven snippets, free & offlineNo sign-up. No tracking. Works offline once loaded.
08 · FAQ
Macrotasks (like setTimeout callbacks) are taken one per event-loop turn. Microtasks (promise .then callbacks, queueMicrotask, await continuations) live in a separate queue that is drained completely after every task — even microtasks queued during the drain run before the next macrotask. That is why a promise callback always beats a setTimeout(0) callback.
setTimeout(fn, 0) run after Promise.then?setTimeout(fn, 0) only queues a macrotask; it never runs immediately. Before the event loop takes the next macrotask it performs a microtask checkpoint that drains the entire microtask queue — so every pending .then callback runs first, even if the timeout was queued earlier.
Real. Every snippet on this page is executed verbatim in a real Node child process (Node v25.4.0, verified 2026-07-23) by the site's own test suite, and the captured output must exactly match the order shown. looptrace applies the same rule to all 36 of its snippets: any snippet its model cannot predict is removed, never patched by hand.
Yes. It is a static page with a strict Content-Security-Policy (connect-src 'none'), so the browser itself blocks any network request. No accounts, no analytics, no tracking; the only stored value is your theme choice, in your browser's localStorage.