Event loop in detail
What is the Event Loop?
The Event Loop is a mechanism that organizes the execution of JS code in a single-threaded environment: it takes tasks, runs them, drains the microtask queue after each task, lets the browser render when needed, then takes the next task. It is the "conductor" between the call stack, the task queues, and rendering.
Simplified (browser):
while (true) {
take one macrotask → run it
while there are microtasks → run them one by one (FIFO)
if possible → run rAF/render
}What does the call stack do?
The call stack is a LIFO structure where functions execute "here and now".
- A function is called - it gets pushed onto the stack.
- A function finishes - it gets popped off the stack.
- As long as the stack isn't empty, no callbacks from the queues can run.
What does the microtask queue do?
The microtask queue is a queue of "urgent" short tasks that run right after the current task (once the stack is empty) and before the next macrotask/render.
- Example sources:
Promise.then/catch/finally,queueMicrotask,MutationObserver(browser),process.nextTick(Node.js: a separate, higher-priority microqueue). - Important: all microtasks run back to back. A long chain of microtasks can block rendering (starvation).
What does the macrotask queue do?
The macrotask queue is a queue of "regular" events/tasks: timers, I/O, UI events.
- Examples (browser):
setTimeout,setInterval,MessageChannel, user events, network callbacks. - Examples (Node.js):
setTimeout/setInterval(timers),setImmediate(check), I/O callbacks, and others. - The Event Loop takes one macrotask, runs it fully, then drains the microtask queue.
How do microtasks differ from macrotasks?
- Timing: microtasks run right after the current task and before rendering; macrotasks run one per "tick" of the loop.
- Priority: microtasks have higher priority (they "slip in" between macrotasks).
- Typical sources: microtasks:
Promise/queueMicrotask; macrotasks:setTimeout, UI/I/O events. - Risk: endless microtasks can delay rendering and interface responsiveness.
What does queueMicrotask() do?
It schedules a callback into the microtask queue:
- It runs after the current stack, before the next macrotask, and before rendering.
- Essentially, it's "like
Promise.then, but without creating an extra promise".
console.log('A');
queueMicrotask(() => console.log('microtask-1'));
Promise.resolve().then(() => console.log('microtask-2'));
setTimeout(() => console.log('macrotask (timeout)'), 0);
console.log('B');
// Output order:
// A
// B
// microtask-1
// microtask-2
// macrotask (timeout)Why does this happen? First the script runs (one macrotask): it prints A, schedules the microtasks, schedules the macrotask, prints B. Then all the microtasks run (queueMicrotask/Promise), and only after that:
setTimeout.
A few more practical notes
- In the browser, rendering usually happens after the microtask queue is drained and between macrotasks. Long microtasks mean "freezes".
- In Node.js the loop has phases (timers → pending callbacks → poll → check → close). Microtasks run after each callback and at the end of a tick.
process.nextTickruns even before regular microtasks: don't abuse it. requestAnimationFrameis not a micro/macrotask; it's a rendering frame that comes after microtasks and before rendering.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.