Q112: Task, Microtask and Rendering Order
What Interviewers Want To Evaluate
This question tests whether you understand browser scheduling beyond basic event loop definitions. Interviewers want tasks, microtasks, rendering opportunities, promise callbacks, timers, queueMicrotask, requestAnimationFrame, and why too many microtasks can delay rendering.
Senior answers should connect scheduling to responsiveness, animation, input delay, and UI state updates.
Short Interview Answer
The browser event loop runs one task, then drains the microtask queue, then may update rendering before the next task. Timer callbacks, user events, and network events usually enter task queues. Promise reactions and queueMicrotask enter the microtask queue. requestAnimationFrame callbacks run before painting. Microtasks are useful for immediate follow-up work, but a long microtask chain can delay rendering and user input.
Detailed Interview Answer
The browser does not run all async callbacks the same way. Different APIs schedule work at different phases.
The simplified order is:
run a task
drain microtasks
run animation frame callbacks
render if needed
run the next task
This model explains many interview output questions and real UI bugs.
Tasks
Tasks are larger units of work. Examples include:
initial script execution
setTimeout callback
setInterval callback
user input event
message event
some network callbacks
Only one task runs at a time. A task must finish before another task can start.
Microtasks
Microtasks run after the current call stack finishes and before the browser proceeds to the next task.
Examples include:
promise then/catch/finally callbacks
queueMicrotask callbacks
MutationObserver callbacks
async function continuations
Microtasks are drained completely before rendering gets a chance.
Basic Ordering Example
console.log("script");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("end");
Output:
script
end
promise
timer
The promise callback runs before the timer because microtasks drain before the next task.
queueMicrotask
queueMicrotask explicitly schedules a microtask.
queueMicrotask(() => {
console.log("after current stack");
});
It is useful when code must run after synchronous work but before the next task.
requestAnimationFrame
requestAnimationFrame schedules work before the browser paints.
requestAnimationFrame(() => {
element.style.transform = "translateX(100px)";
});
This is useful for animation and DOM reads/writes that should align with rendering.
Rendering Opportunity
Browsers usually render between tasks after microtasks are drained.
If a task takes too long, rendering is delayed.
If microtasks keep adding more microtasks, rendering is also delayed.
function spin() {
queueMicrotask(spin);
}
spin();
This can starve rendering because the microtask queue never empties.
Animation Timing
For visual updates, prefer requestAnimationFrame over timers.
Timers are not aligned to paint and can run at awkward times.
requestAnimationFrame gives the browser a chance to coordinate work with frame rendering.
Splitting Long Work
Long synchronous work blocks tasks, microtasks, input, and rendering.
For expensive work:
move CPU work to a Web Worker
chunk work across tasks
use requestIdleCallback for non-urgent work where supported
yield back to the browser
avoid large microtask loops
The correct option depends on urgency and user impact.
React Connection
React state updates, effects, and rendering are scheduled by React's own system, but the browser still controls the final event loop and paint behavior.
Heavy synchronous code in event handlers can still delay input responsiveness.
Common Mistakes
A common mistake is saying setTimeout(..., 0) means immediate execution. It means schedule a future task as soon as allowed.
Another mistake is assuming promises render immediately. Promise callbacks are microtasks and may run before paint.
Senior Trade-Offs
Use microtasks for small consistency work. Use tasks to yield to the browser. Use animation frames for visual work. Use workers for heavy CPU work.
The goal is not memorizing queues; it is protecting interactivity.
Code or Design Example
function updateProgressInChunks(items, processItem) {
let index = 0;
function runChunk() {
const deadline = performance.now() + 8;
while (index < items.length && performance.now() < deadline) {
processItem(items[index]);
index += 1;
}
if (index < items.length) {
setTimeout(runChunk, 0);
}
}
runChunk();
}
This yields between chunks so the browser can process input and rendering.
Debugging Workflow
When the UI feels stuck:
record a performance trace
look for long tasks
look for recursive microtasks
check promise-heavy loops
check animation work outside requestAnimationFrame
check whether CPU work belongs in a worker
check input delay and rendering gaps
Interview Framing
Give the order: task, microtasks, rendering opportunity, next task. Then show a code-ordering example and a production performance implication.
Revision Notes
Microtasks run before the browser moves to the next task and before rendering opportunities. Too much microtask work can delay paint.
Key Takeaways
Scheduling is user experience. The right queue choice affects responsiveness, animation smoothness, and perceived performance.
Follow-Up Questions
- What is a task?
- What is a microtask?
- Why do promises run before timers?
- When does rendering happen?
- What does
queueMicrotaskdo? - Why can microtasks starve rendering?
- When should you use
requestAnimationFrame? - Why is
setTimeout(..., 0)not immediate? - How do you split long work?
- How does scheduling affect INP?
How I Would Answer This In A Real Interview
I would explain that the browser runs a task, drains microtasks, then may render. I would show promise-before-timer ordering, then connect it to performance by explaining how long tasks and endless microtasks block paint and input.