Short answer
JavaScript runs your code on a single call stack. Asynchronous work such as timers, network requests and I/O is handled by the host environment, and its callbacks wait in queues. The event loop moves a callback onto the stack only when the stack is empty, always draining the microtask queue (promises) before taking the next macrotask (timers, events).
The moving parts
- Call stack — where synchronous code executes, one frame at a time.
- Host APIs — the browser or Node.js runs timers, network and file I/O outside the stack.
- Macrotask queue — callbacks from setTimeout, setInterval, I/O and UI events.
- Microtask queue — promise reactions (then/catch/finally), queueMicrotask and MutationObserver.
Ordering in practice
After each macrotask, the engine runs every queued microtask before rendering or picking the next macrotask. That is why a resolved promise callback runs before a setTimeout of zero milliseconds.
javascriptconsole.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// Output: A, D, C, BWhy it matters
Long synchronous work blocks the stack, so nothing else — including rendering and input handling — can run. Splitting heavy work into chunks, moving it to a Web Worker, or using worker threads in Node.js keeps applications responsive.
How to answer it in an interview
- Walk through an ordering example out loud; interviewers love predicting output.
- Mention that an endless chain of microtasks can starve rendering.
- Note that Node.js adds phases (timers, poll, check) and process.nextTick, which runs before promise microtasks.