JavaScript is single-threaded: async callbacks wait in queues. Microtasks (promises) are fully drained before any macrotask (setTimeout). So a promise scheduled LATER still runs before a 0ms timer scheduled earlier. Run it and read the order.
Explicit over implicit
🔑 실무 용어 (say these in English)
event loop — one thread runs code, then drains queued callbacks“JS is single-threaded — the event loop runs my code, then drains queued callbacks.”
microtask vs macrotask — promises run before timers“Promise callbacks are microtasks and drain before any setTimeout macrotask.”
call stack — sync frames run to completion first“Synchronous frames run to completion on the call stack before the loop continues.”
non-blocking I/O — async work yields so the thread stays free“Async I/O yields the thread so the UI stays responsive.”
⚖️ Trade-offs · 원리 (say these out loud)
A resolved promise's callback runs on the microtask queue, which the event loop fully drains after the current synchronous frame but BEFORE rendering, I/O, or any macrotask, whereas setTimeout(0) is a macrotask clamped to a ~4ms minimum and queued behind already-pending timers and rendering — so a promise wins precisely because microtasks have strictly higher priority than the timer phase.
The cost of that priority is starvation: because the loop empties the entire microtask queue before yielding, a chain of promises that keep enqueuing more microtasks can block rendering and starve I/O indefinitely, which is exactly the failure mode setTimeout(0) avoids by surrendering control back to the loop after one task.
Reach for queueMicrotask/Promise.resolve().then() when you need to defer work but still land before the next paint — e.g. batching state mutations or guaranteeing a callback runs after the current call stack unwinds without ever yielding to the browser.
Use setTimeout(0) (or better, requestAnimationFrame for visual work, or MessageChannel to dodge the 4ms clamp) precisely when you WANT to yield — to let the browser paint, process input, or break up a long task so the main thread stays responsive; choosing a microtask there is the bug, not the optimization.