Question presented to candidate: "What is actually the difference between a microtask and a macrotask in JavaScript -- not just one example, but which APIs belong to which queue, and what does draining actually mean?"
What a strong answer should cover:
- Microtasks: Promise reactions (.then/.catch/.finally), queueMicrotask() callbacks, an await resumption, and (per the DOM spec) MutationObserver callbacks.
- Macrotasks (also called tasks): setTimeout/setInterval callbacks, I/O completions, UI events like clicks, and in a browser, rendering/layout steps between tasks; Node.js adds setImmediate as an extra macrotask-like phase.
- The defining behavioral difference is not what each queue contains, it is how much runs per pass: the ENTIRE microtask queue drains every time, including microtasks added during the drain, while only ONE macrotask runs before the loop checks microtasks again.
- Node.js layers two extra, Node-only queues on top of the standard model: process.nextTick (drains before the standard microtask queue, every time) and setImmediate (a macrotask-like phase with its own ordering rules relative to timers).
- A common, real consequence: code that keeps re-scheduling microtasks from within microtasks can starve every macrotask indefinitely, since the macrotask queue is never even checked until the microtask queue reports empty.
Clarifying questions expected:
- "Do you want browser-only APIs like MutationObserver included, or should I keep this to what is common across browsers and Node.js?"
Code / implementation expected: Yes -- a short, runnable snippet demonstrating queueMicrotask, Promise.then, and setTimeout draining in the right relative order.