Fundamentalsintermediate
The Event Loop and Call Stack
How JavaScript's single-threaded call stack cooperates with the event loop, task queue, and microtask queue to handle asynchronous work.
JavaScript runs one thing at a time and still handles timers, requests and clicks without freezing. The event loop is how.
Think of the call stack as a single cashier who can only serve one customer at a time. The microtask queue is VIP customers who get served completely before the cashier looks at the regular line (macrotask queue) again — even if a VIP walks in while another VIP is already being helped.
Key Concepts
1
The call stack runs synchronous code. A call pushes a frame; a return pops it. Nothing else can run
while the stack has frames on it.
2
Asynchronous work is handed off. setTimeout, a network request, a DOM event — these go to the browser
(or libuv in Node), which is not the JavaScript thread. When they finish, a callback is placed in a queue.
setTimeout
3
There are two queues, and the order between them is the whole topic.
macrotask queue setTimeout, setInterval, DOM events, I/O
microtask queue Promise callbacks, queueMicrotask,
MutationObserver4
The loop's rule: when the stack empties, drain the entire microtask queue, then take one
macrotask, then drain microtasks again.
5
Which produces the output people are asked to predict.
console.log('1');
setTimeout(() => console.log('2'));
Promise.resolve().then(() => console.log('3'));
console.log('4');6
// 1 4 3 27
1 and 4 are synchronous. Then the stack empties. 3 is a microtask and runs before 2, which is a
macrotask — even though the timeout was scheduled first.
1432
8
Microtasks can starve the loop.
function loop() { Promise.resolve().then(loop); }
loop(); // the page freezes9
Each microtask queues another, and the queue is drained completely before anything else runs. So the
rendering and the timers never get a turn. A recursive setTimeout does not do this, because each one is a
separate macrotask.
setTimeout
10
setTimeout(fn, 0) is not zero. It means "queue this as a macrotask", and the browser clamps nested
timeouts to about 4 ms. It runs after the current synchronous code and after all pending microtasks.
setTimeout(fn, 0)
11
Why blocking matters. A long synchronous loop holds the stack, so no callback runs, no event is
handled, and the page cannot even repaint.
12
for (let i = 0; i < 1e10; i++) {} // the tab is frozen13
Breaking such work into chunks with setTimeout, or moving it to a Web Worker, is the fix — because the
constraint is the single thread, not the algorithm.
setTimeout
14
What the interviewer is probing.1. "Walk through what happens when you call setTimeout with 0ms." Probing: the queue model.
Stalls: "It runs immediately." Moves up: the callback goes to the task queue and runs only after
the stack empties and all pending microtasks drain.
15
2. "Which runs first, a promise callback or a setTimeout?" Probing: microtasks versus
macrotasks. Stalls: "Whichever was scheduled first." Moves up: the promise — the microtask queue
drains completely between every macrotask.
16
3. "What blocks the event loop?" Probing: the single thread. Stalls: "Nothing, it is async."
Moves up: any synchronous work — a long loop or a huge JSON parse — because the loop cannot
proceed until the stack clears.
17
4. "How do you run heavy computation without freezing the UI?" Probing: the escape hatches.
Stalls: "Use async/await." Moves up: a Web Worker on another thread, or chunk the work and yield
between pieces; async does not create parallelism.