Task queue in JavaScript
The task queue is a data structure that holds tasks (callbacks) waiting to run once the call stack is free. It is a key part of the Event Loop machinery in JavaScript.
Theory
TL;DR
- The task queue holds callbacks that are ready to run but are waiting for the call stack to become free.
- JavaScript is single threaded, so only one task runs at a time.
- The Event Loop takes the first task from the queue (FIFO) and pushes it onto the stack as soon as the stack is empty.
- There are in fact two queues: the macrotask queue (
setTimeout, DOM events) and the microtask queue (Promise.then,queueMicrotask). - Microtasks always run before the next macrotask.
- The order is: the stack, then microtasks, then macrotasks.
Quick example
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
console.log('3');Console output:
1
3
2The key idea
JavaScript is a single threaded language, it can run only one task at a time. While the engine is busy running functions on the call stack, all the other tasks, for example click handlers, setTimeout, network callbacks and so on, wait their turn in the task queue.
When the call stack becomes empty, the Event Loop takes the first task from the queue and places it on the stack. And so it continues, endlessly.
Walking through the example
console.log('1')runs immediately and prints1.setTimeout(...): the browser starts a timer. When the timer fires, the callback lands in the task queue rather than running instantly.console.log('3')runs and prints3.- Once the call stack is empty, the Event Loop takes the task from the queue (the
setTimeoutcallback) and places it on the stack, which prints2.
This is why a zero delay in setTimeout means "as soon as possible after the stack is free", not "right now".
Task queue vs microtask queue
JavaScript has two queues:
| Queue type | Source of tasks | When they run |
|---|---|---|
| Task Queue (macro tasks) | setTimeout, setInterval, DOM events, I/O callbacks and so on | After the stack and all microtasks are cleared |
| Microtask Queue | Promise.then, queueMicrotask, MutationObserver | Right after the current task, before the next macrotask |
The difference matters: the microtask queue is drained completely, while only one task is taken from the macrotask queue per turn of the loop.
Example with microtasks
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');Output order:
A
D
C
BExplanation:
A,Drun immediately, this is synchronous code on the call stack.Cis a microtask, so it runs right after the stack is cleared.Bis a macrotask, so it runs after all microtasks.
Terms at a glance
| Term | Description |
|---|---|
| Task Queue | The macrotask queue, it holds callbacks from setTimeout, events and so on |
| Microtask Queue | The microtask queue, for Promise.then, queueMicrotask |
| Event Loop | The mechanism that watches the stack and the queues and decides what runs next |
| Order | First the stack, then microtasks, then macrotasks |
Common mistakes
- Assuming
setTimeout(fn, 0)runsfnimmediately. The callback only joins the task queue and waits for the stack to empty. - Confusing the task queue with the call stack. The stack is LIFO and holds what runs now; the queue is FIFO and holds what will run later.
- Treating promises and timers as equals.
Promise.thengoes to the microtask queue and always beats anysetTimeout, even one with a zero delay. - Spawning microtasks endlessly. If a microtask keeps scheduling another microtask, the queue never drains and macrotasks and rendering never get control.
- Thinking the queue makes code parallel. Execution still goes through one thread and one stack, just in a different order.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.