Explain the JavaScript event loop and how it works
JavaScript is single-threaded with one call stack. Async work (timers, fetch, events) is handed to Web APIs / the host. When it completes, a callback is queued. The event loop runs: when the call stack is empty, it drains ALL microtasks (Promises, queueMicrotask), then takes ONE macrotask (timer, I/O, event) and repeats — rendering can happen between macrotasks.
JavaScript runs on a single thread with one call stack. The event loop is the mechanism that lets a single-threaded language do non-blocking async work.
The pieces
- Call stack — where function frames are pushed/popped. Only one thing runs at a time.
- Web APIs / host environment — the browser (or Node) provides
setTimeout,fetch, DOM events, etc. These run outside the JS engine, often on other threads. - Macrotask queue (task queue) — callbacks from
setTimeout,setInterval, I/O, UI events,MessageChannel. - Microtask queue — callbacks from resolved Promises (
.then/awaitcontinuations),queueMicrotask,MutationObserver. - Event loop — the coordinator.
The algorithm
while (true) {
// 1. Run the current macrotask to completion (synchronous code runs here)
// 2. Drain the ENTIRE microtask queue
// (microtasks added during this step also run, before moving on)
// 3. Render if needed (style, layout, paint) — browser only, ~60fps
// 4. Take ONE macrotask from the queue, go to 1
}Two rules that explain almost every interview puzzle:
- The stack must be empty before the loop picks up any queued callback.
- All microtasks drain before the next macrotask — and before rendering.
Canonical example
console.log("1");
setTimeout(() => console.log("2"), 0); // macrotask
Promise.resolve().then(() => console.log("3")); // microtask
console.log("4");
// Output: 1, 4, 3, 21 and 4 are synchronous. The stack empties. Microtasks drain → 3. Then one macrotask → 2.
Why it matters
- A long synchronous task blocks everything — no events, no rendering, no timers. (This is why you offload heavy work to Web Workers or chunk it.)
- A microtask that keeps queuing microtasks can starve the macrotask queue and rendering — an infinite
.thenloop freezes the page. setTimeout(fn, 0)doesn't run "immediately" — it runs after the current task and all microtasks, and not before the minimum clamp (~4ms for nested timers).
Senior framing
The senior answer ties it to UX: the event loop is why JS feels responsive despite being single-threaded, and why one bad synchronous loop kills frame rate. Knowing that microtasks fully drain before rendering — and that this is how Promise-heavy code can still jank the UI — is the depth interviewers look for.
Follow-up questions
- •What's the difference between a microtask and a macrotask?
- •Why does setTimeout(fn, 0) not run immediately?
- •How can microtasks starve the event loop?
- •Where does rendering fit in the loop?
Common mistakes
- •Thinking setTimeout(fn, 0) runs before resolved Promises.
- •Believing JS is multi-threaded because async exists.
- •Forgetting the call stack must be empty before any callback runs.
- •Assuming rendering happens between every microtask.
Edge cases
- •Nested setTimeout calls are clamped to ~4ms minimum after the 5th level.
- •An unbounded microtask chain blocks rendering and macrotasks entirely.
- •Node's event loop has distinct phases (timers, poll, check) — not identical to the browser's.
Real-world examples
- •Why a heavy synchronous JSON parse freezes the UI; why Promise.then chains run before timers.