Callback Queue
The Macrotask Line
LinkedIn Hook
Every
setTimeout, everyclickhandler, everysetIntervaltick — they all stand in the same line.That line is called the Callback Queue (also known as the Task Queue or Macrotask Queue), and it has exactly one rule: first in, first out. But here's the twist interviewers love to exploit — the Event Loop only picks one macrotask per tick, and it checks the microtask queue between every single pick.
That's why three
setTimeout(fn, 0)calls don't run "together." They run one at a time, interleaved with microtasks, with the browser potentially repainting in between.If you've ever wondered why
setInterval(fn, 1000)is not actually every 1000ms, or why your click handler feels laggy when the page is busy — the Callback Queue is the reason.In this lesson you'll see the FIFO rule in action, walk through the "one-macrotask-per-tick" behavior, and understand why
setIntervaltiming is a suggestion, not a guarantee.Read the full lesson -> [link]
#JavaScript #EventLoop #Macrotask #CallbackQueue #InterviewPrep #AsyncJS #WebDevelopment
What You'll Learn
- What goes into the Callback Queue (macrotasks) and why order matters
- The "one macrotask per Event Loop tick" rule
- Why
setIntervalis never guaranteed to fire exactly on time
FIFO — First In, First Out
The Callback Queue (also called the Task Queue or Macrotask Queue) is where callbacks from setTimeout, setInterval, DOM events, and I/O operations wait to be executed.
Think of it like a line at a bank. People (callbacks) join the line when they arrive, and they're served in FIFO order (First In, First Out). But the teller (call stack) must finish with the current customer before calling the next one.
What Goes Into the Callback Queue?
| Source | Example |
|---|---|
setTimeout | setTimeout(() => {...}, 1000) |
setInterval | setInterval(() => {...}, 500) |
| DOM Events | button.addEventListener('click', handler) |
| I/O Operations | File reads (Node.js), XMLHttpRequest |
setImmediate | Node.js only |
MessageChannel | port.onmessage |
FIFO Order in Action
setTimeout(() => console.log("First timeout"), 0);
setTimeout(() => console.log("Second timeout"), 0);
setTimeout(() => console.log("Third timeout"), 0);
console.log("Synchronous");
Output:
Synchronous
First timeout
Second timeout
Third timeout
All three setTimeout callbacks enter the Callback Queue in order. The Event Loop processes them one at a time, FIFO.
Important: One Macrotask Per Event Loop Tick
The Event Loop processes one macrotask per tick, then checks and drains the entire microtask queue before picking the next macrotask.
setTimeout(() => {
console.log("Macrotask 1");
}, 0);
setTimeout(() => {
console.log("Macrotask 2");
}, 0);
Promise.resolve().then(() => {
console.log("Microtask 1");
});
console.log("Sync");
Output:
Sync
Microtask 1
Macrotask 1
Macrotask 2
Step-by-step:
- Both
setTimeoutcallbacks -> Callback Queue (macrotask) - Promise
.then-> Microtask Queue "Sync"-> prints immediately- Call stack empty -> drain Microtask Queue first -> "Microtask 1"
- Pick one macrotask -> "Macrotask 1"
- Check microtask queue (empty) -> pick next macrotask -> "Macrotask 2"
setInterval — Repeated Macrotasks
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Interval tick: ${count}`);
if (count === 3) {
clearInterval(intervalId);
console.log("Interval cleared");
}
}, 1000);
console.log("Interval registered");
Output:
Interval registered
Interval tick: 1 <- after ~1 second
Interval tick: 2 <- after ~2 seconds
Interval tick: 3 <- after ~3 seconds
Interval cleared
Each interval tick adds a new callback to the Callback Queue. If the call stack is busy when a tick fires, the callback waits — intervals are not guaranteed to be exactly on time.
Common Mistakes
- Thinking three
setTimeout(fn, 0)calls run "in parallel." They run strictly one after the other in FIFO order, with microtask draining in between. - Expecting
setInterval(fn, 1000)to fire exactly every 1000ms. If the stack is busy, callbacks bunch up or drop — use recursivesetTimeoutorrequestAnimationFramefor precision. - Confusing the Callback Queue with the Call Stack. The stack is LIFO and synchronous; the queue is FIFO and async.
Interview Questions
Q: What is the Callback Queue?
The Callback Queue (also called Task Queue or Macrotask Queue) is a FIFO queue where callbacks from Web APIs (setTimeout, setInterval, DOM events, I/O) wait for the Event Loop to push them onto the call stack. The Event Loop only moves a callback to the call stack when the stack is completely empty and all microtasks have been processed.
Q: What's the difference between the Callback Queue and the Call Stack?
The Call Stack is where JavaScript executes code — it's LIFO (Last In, First Out) and runs synchronously. The Callback Queue is where async callbacks wait — it's FIFO (First In, First Out). The Event Loop transfers tasks from the queue to the stack, but only when the stack is empty.
Q: Does setInterval guarantee exact timing?
No.
setInterval(fn, 1000)means "try to add a callback every ~1000ms," but if the call stack is busy when a tick fires, the callback waits in the queue. This can lead to callbacks bunching up or being delayed. For precise timing needs, userequestAnimationFrameor recursivesetTimeout.
Q: How many macrotasks does the Event Loop process per tick?
Exactly one. After each macrotask finishes, the Event Loop drains the entire microtask queue before returning to pick the next macrotask.
Quick Reference — Cheat Sheet
CALLBACK QUEUE (MACROTASK) — QUICK MAP
Order: FIFO (first in, first out)
Per tick: ONE macrotask, then drain microtasks, repeat
Sources:
setTimeout / setInterval
DOM events (click, scroll, keydown, ...)
I/O (XHR, fs.readFile in Node)
setImmediate (Node)
MessageChannel (port.onmessage)
Timing warning:
setInterval(fn, 1000) != exactly every 1000ms
busy stack -> callback waits -> bunching/dropping possible
Previous: Web APIs & Node APIs -> The Runtime's Toolbox Next: Microtask Queue -> The Priority Lane
This is Lesson 3.3 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.