Three places work can wait
The call stack runs the code currently executing. The task queue holds work like timers, events, and message callbacks. The microtask queue holds promise continuations and queueMicrotask callbacks.
After the current stack empties, JavaScript flushes microtasks before taking the next task. This is the detail that explains most surprising Promise vs setTimeout examples.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
// A, D, C, BRendering is not every line of code
The browser gets chances to render between tasks, after microtasks have been flushed. If you keep filling the microtask queue, you can delay rendering even though you are not using a visible loop.
This matters for loading states and animations. Setting state and immediately doing heavy synchronous work may prevent the user from seeing the state you thought you showed.
Try the idea
- 1script: A
- 2script: D
- 3microtask: Promise.then C
- 4task: setTimeout B
await creates a continuation
Code before await runs in the current stack. Code after await continues later as a microtask when the awaited promise settles. This is why an async function can look sequential while still yielding back to the event loop.
async function run() {
console.log("before");
await Promise.resolve();
console.log("after");
}Production checks
Event-loop bugs show up as frozen buttons, delayed loading states, broken animations, or callbacks running in an order no one expected.
Production notes for JavaScript apps
When this concept moves from a tutorial into a real app, the important question is not whether the example works once. It is whether the behavior is still predictable when network latency, retries, user interaction, rendering, and logging all happen together.
Keep the boundary visible. Name the queue, state, error, or ownership rule in the code so the next developer can understand why the behavior exists without reading the whole feature from top to bottom.
Conclusion
The event loop is not a vibe. It is a scheduling contract. Once you can predict the queues, async code stops feeling random.

