Event loop interview questions
Every question here is answered by an animation you can step through β so if you get one wrong, you can watch exactly where your model diverged. Predict first; the reveal is one click away.
warm-up The model everything else is built on.
What order do the logs appear in?
One thing at a time
1function makeCoffee() {2 console.log('coffee ready');3}45console.log('open the shop');6makeCoffee();7console.log('close the shop');- Aopen the shop β coffee ready β close the shop
- Bcoffee ready β open the shop β close the shop
- Copen the shop β close the shop β coffee ready
Reveal the answer βAnswer
A. open the shop β coffee ready β close the shop
Before any of the async machinery makes sense, the call stack has to. JavaScript runs on a single thread with a single stack: calling a function pushes a frame on top, returning pops it off, and only the topmost frame is ever running.
Watch it run, step by step βWhat does the 0 in setTimeout(fn, 0) actually mean?
setTimeout(0), alone
1console.log('before');23setTimeout(() => {4 console.log('later');5}, 0);67console.log('after');- ARun in exactly 0 milliseconds, guaranteed
- BRun before any other console.log
- CRun immediately, interrupting the current line
- DRun after the current code finishes β at the earliest
Reveal the answer βAnswer
D. Run after the current code finishes β at the earliest
setTimeout(fn, 0) is the shortest possible demonstration that a delay is a minimum, not a schedule. The callback does not run in zero milliseconds, and it does not run "as soon as possible" in any intuitive sense β it runs when the call stack is empty and the loop reaches it.
Watch it run, step by step βPromise.resolve() is already resolved. When does its .then callback run?
A promise, alone
1console.log('start');23Promise.resolve().then(() => {4 console.log('from the promise');5});67console.log('end');- AAfter a minimum 4ms delay, like a timer
- BAfter all the synchronous code finishes
- COnly if you await it
- DImmediately, on the same line
Reveal the answer βAnswer
B. After all the synchronous code finishes
A promise that is already resolved still does not call your .then callback right away. Promise.resolve() has nothing to wait for, and yet "from the promise" prints after "end". That surprises people, and the reason is worth being precise about.
Watch it run, step by step β
core The ones that actually get asked.
What order do the logs appear in?
Promise vs setTimeout
1console.log('start');23setTimeout(() => {4 console.log('timeout');5}, 0);67Promise.resolve().then(() => {8 console.log('promise');9});1011console.log('end');- Astart β end β timeout β promise
- Bstart β timeout β promise β end
- Cstart β end β promise β timeout
- Dstart β promise β end β timeout
Reveal the answer βAnswer
C. start β end β promise β timeout
This is the interview question, and the answer is start, end, promise, timeout. Both callbacks are scheduled at essentially the same moment, and the timer even becomes ready first β yet the promise callback still wins.
Watch it run, step by step βWhat order do the logs appear in?
.then after .then
1Promise.resolve()2 .then(() => {3 console.log('first');4 })5 .then(() => {6 console.log('second');7 });89console.log('sync');- Afirst β second β sync
- Bsync β second β first
- Csync β first β second
- Dfirst β sync β second
Reveal the answer βAnswer
C. sync β first β second
A .then chain is not a single unit of work. Each link is its own microtask, scheduled only when the previous link has produced a value β which is why "sync" prints first, then "first", then "second", instead of the whole chain resolving in one go.
Watch it run, step by step βWhat does this loop log?
setTimeout inside a for loop
1for (var i = 0; i < 3; i++) {2 setTimeout(() => {3 console.log(i);4 }, 0);5}67console.log('loop finished, i =', i);- A0, 1, 2
- B3, 3, 3
- C1, 2, 3
- D0, 0, 0
Reveal the answer βAnswer
B. 3, 3, 3
This is the most-asked JavaScript interview question that is really two questions wearing one coat: a closure question and an event loop question. The closure half is why every callback sees the same i. The event loop half is why every callback runs after the loop has already finished.
Watch it run, step by step βWhat order do the logs appear in?
async/await, unmasked
1async function order() {2 console.log('ordering');3 await Promise.resolve();4 console.log('order arrived');5}67order();8console.log('walk away');- Aordering β walk away β order arrived
- Bordering β order arrived β walk away
- Cwalk away β ordering β order arrived
Reveal the answer βAnswer
A. ordering β walk away β order arrived
async and await are syntax over the same promise machinery, and this scene shows the moment that makes it obvious: the function leaves the call stack mid-body. "ordering" prints, the function hits await, and it stops β control returns to the caller, which is how "walk away" prints before "order arrived".
Watch it run, step by step βRoughly how long until "done: a, b" prints?
Promise.all β waiting in parallel
1function wait(ms, label) {2 return new Promise((resolve) => {3 setTimeout(() => resolve(label), ms);4 });5}67const a = wait(100, 'a');8const b = wait(100, 'b');9console.log('both timers already running');1011Promise.all([a, b]).then((results) => {12 console.log('done:', results.join(', '));13});- A~200ms β the waits run one after another
- B~0ms β Promise.all does not wait
- C~100ms, but the results may print as "b, a"
- D~100ms β the two waits overlap
Reveal the answer βAnswer
D. ~100ms β the two waits overlap
The mistake this scene fixes is believing that await starts the work. It does not. Calling wait(100, "a") starts the timer immediately, because the Promise executor runs synchronously. await only decides when you stop and collect the result.
Watch it run, step by step βWhen the response arrives, where does the .then callback go?
fetch β a real network wait
1console.log('requesting');23fetch('/api/user')4 .then((res) => res.json())5 .then((user) => {6 console.log('got', user.name);7 });89console.log('not waiting');- AThe task queue, behind any waiting setTimeout callbacks
- BIt stays in Web APIs until the next repaint
- CThe microtask queue β it runs before any waiting timer callback
- DStraight onto the call stack, interrupting whatever is running
Reveal the answer βAnswer
C. The microtask queue β it runs before any waiting timer callback
fetch is the clearest demonstration of why the event loop exists. The network takes tens or hundreds of milliseconds, and the main thread spends none of it waiting. The request is handed to the browser, the stack empties, and the page stays responsive to clicks and animation the whole time.
Watch it run, step by step βTwo fast clicks arrive while the stack is busy. What happens?
Clicks are just tasks
1button.addEventListener('click', () => {2 console.log('clicked');3});45console.log('listening');67// the user now clicks twice, fast- ABoth handlers queue as tasks and run one per loop turn, in order
- BBoth handlers run as microtasks, before any pending timer
- CThe handlers interrupt the running code immediately
- DThe second click is dropped because the first has not run yet
Reveal the answer βAnswer
A. Both handlers queue as tasks and run one per loop turn, in order
Event listeners feel special β they seem to fire "when the user does something" β but the event loop treats them like everything else. The browser detects the click, then queues your listener as a task. It waits in the same queue as setTimeout callbacks, behind whatever is already there.
Watch it run, step by step β
advanced Where mental models usually break.
What is the output order?
The checkpoint runs between every task
1setTimeout(() => {2 console.log('task 1');3 Promise.resolve().then(() => console.log('micro 1'));4}, 0);56setTimeout(() => {7 console.log('task 2');8 Promise.resolve().then(() => console.log('micro 2'));9}, 0);- Amicro 1 β micro 2 β task 1 β task 2
- Btask 1 β micro 1 β micro 2 β task 2
- Ctask 1 β micro 1 β task 2 β micro 2
- Dtask 1 β task 2 β micro 1 β micro 2
Reveal the answer βAnswer
C. task 1 β micro 1 β task 2 β micro 2
Most people learn "microtasks run before tasks" from the classic promise-vs-setTimeout example and quietly generalise it into "all microtasks run, then all tasks". This scene breaks that. The microtask queue is drained after every single task, not once at the end of the script.
Watch it run, step by step βWhen does "my table?!" print?
Starving the loop
1function feedVIPs() {2 queueMicrotask(feedVIPs);3}45setTimeout(() => {6 console.log('my table?!');7}, 0);89queueMicrotask(feedVIPs);- ANever β the microtask queue never empties
- BAfter the first feedVIPs finishes
- CAfter roughly 4ms, like any clamped timer
- DImmediately β tasks have priority over microtasks
Reveal the answer βAnswer
A. Never β the microtask queue never empties
Microtask priority is usually described as a feature. This scene shows the failure mode on the other side of it: a microtask that queues another microtask, forever. The checkpoint is defined to drain until the queue is empty, and this queue is never empty, so the checkpoint never ends.
Watch it run, step by step βWhat is the output order?
requestAnimationFrame vs setTimeout
1console.log('start');23setTimeout(() => {4 console.log('timeout');5}, 0);67requestAnimationFrame(() => {8 console.log('rAF β right before paint');9});1011Promise.resolve().then(() => console.log('promise'));- Astart β promise β rAF β timeout
- Bstart β timeout β promise β rAF
- Cstart β rAF β promise β timeout
- Dstart β promise β timeout β rAF
Reveal the answer βAnswer
D. start β promise β timeout β rAF
requestAnimationFrame is not a queue you have met so far. It is neither a task nor a microtask: the callback goes on the document's animation frame list, and the browser services that list during its render step β after the current task and its microtask checkpoint, immediately before style, layout and paint.
Watch it run, step by step βThe timer expired after 0ms. When does "am I late?" print?
Blocking the loop
1setTimeout(() => {2 console.log('am I late?');3}, 0);45console.log('start spinning');67while (true) {8 // spinning foreverβ¦9}- ANever β the while loop never lets the event loop take a turn
- BRight after "start spinning"
- CWhen the browser force-kills the loop after ~5 seconds
- DAbout 0ms after the loop starts, interrupting it
Reveal the answer βAnswer
A. Never β the while loop never lets the event loop take a turn
Every other example on this site is about ordering. This one is about the thing ordering depends on: the loop only gets a turn when the call stack is empty, and a synchronous loop never lets that happen.
Watch it run, step by step βIn Node, what is the output order?
Node: nextTick, promises, timers, immediate
1setTimeout(() => console.log('timeout'), 0);2setImmediate(() => console.log('immediate'));34process.nextTick(() => console.log('nextTick'));5Promise.resolve().then(() => console.log('promise'));67console.log('sync');- Async β timeout β immediate β nextTick β promise
- Bsync β nextTick β promise β immediate β timeout
- Csync β promise β nextTick β timeout β immediate
- Dsync β nextTick β promise β timeout β immediate
Reveal the answer βAnswer
D. sync β nextTick β promise β timeout β immediate
Node runs the same JavaScript semantics as the browser but a different loop underneath. Instead of "run one task, drain microtasks", libuv walks a fixed round of phases β timers, pending callbacks, idle/prepare, poll, check, close callbacks β and each phase has its own queue of callbacks to run.
Watch it run, step by step β
Everything else people ask
Each one is answered on the page that animates it.
- What is the call stack in JavaScript?
- Is JavaScript single-threaded?
- Does synchronous code ever involve the event loop?
- Does setTimeout with 0 delay run immediately?
- What is setTimeout(fn, 0) actually used for?
- Why is the minimum setTimeout delay sometimes 4ms?
- Why does a resolved promise callback still run last?
- Can a .then callback ever run synchronously?
- What is the microtask queue?
- Why does a promise callback run before setTimeout?
- What is the difference between a task and a microtask?
- Does this ordering change if the timeout is larger than zero?
- Is each .then in a chain a separate microtask?
- Can a setTimeout callback run in the middle of a promise chain?
- Why does the synchronous log print before any .then callback?
- Why does setTimeout in a for loop print the last value?
- How do I make setTimeout in a loop print 0, 1, 2?
- Does setTimeout with 0 milliseconds run immediately?
- Does await block the main thread?
- What happens to code after an await?
- Does awaiting an already-resolved value still cost a tick?
- Does Promise.all run the promises in parallel?
- Why is await in a for loop slower than Promise.all?
- Does Promise.all return results in completion order?
- Is fetch a macrotask or a microtask?
- Why does fetch need two .then calls?
- Does an await on fetch block the page?
- Are DOM event handlers macrotasks or microtasks?
- Why does my page ignore clicks during a long function?
- Do rapid clicks get merged into one event?
- Do all microtasks run before all macrotasks?
- When exactly does the microtask checkpoint run?
- Can a microtask delay a setTimeout callback?
- What is microtask starvation?
- Can a promise chain freeze the browser?
- How do I yield to the event loop from a repeating job?
- Is requestAnimationFrame a macrotask or a microtask?
- Should I use requestAnimationFrame or setTimeout for animation?
- Does the browser render after every task?
- Why does a while loop freeze the whole page?
- Do timers still fire while JavaScript is blocked?
- How do I run heavy computation without freezing the UI?
- What is the difference between process.nextTick and setImmediate?
- Does process.nextTick run before a promise callback?
- Why is setTimeout versus setImmediate order unpredictable in Node?
Got a snippet of your own you cannot explain? Paste it into the playground β