Event Loop Interview Q&A — Browser and Node.js

Use these notes together:

Core interview answer

The event loop is the host runtime’s scheduling mechanism around the JavaScript call stack. JavaScript runs to completion on a main thread, while the browser or Node.js handles timers, I/O, and other work outside the active stack. When that work is ready, its callback is queued for later execution. Microtasks run at high-priority checkpoints before the runtime continues to another task or event-loop phase. The browser also coordinates rendering, while Node.js uses libuv phases and adds APIs such as process.nextTick() and setImmediate().

2–3 minute staff-level interview script

“The event loop is how a JavaScript runtime coordinates single-threaded JavaScript with asynchronous work.

JavaScript runs one piece of code at a time on the main call stack, and each piece runs to completion. If I start a network request, timer, or file read, the runtime handles the waiting outside that stack. When the operation is ready, its callback does not run immediately—it becomes eligible to run when the stack is free and the scheduler reaches the appropriate queue.

A useful distinction is between regular scheduled work and microtasks. Promise callbacks and code after await are microtasks. Once the current JavaScript finishes, the runtime drains microtasks before moving to the next timer, I/O callback, or browser task. That explains why a resolved Promise normally runs before setTimeout(..., 0), even when the timeout is zero.

The details differ by environment. In the browser, the event loop coordinates JavaScript with user input and rendering. The browser gets opportunities to calculate layout and paint between tasks, after microtasks have drained. So a long task does not just delay another callback—it can block clicks, animation, and the next frame.

In Node.js, there is no rendering pipeline. The event loop is built around libuv and moves through phases for timers, I/O polling, setImmediate(), and close callbacks. Node also has process.nextTick(), which runs ahead of normal Promise microtasks. Network I/O is generally handled through the operating system, while some file-system, crypto, DNS, and compression work uses libuv’s worker pool.

The important engineering point is that asynchronous does not automatically mean non-blocking. Waiting may happen elsewhere, but the callback eventually returns to the main JavaScript thread. If that callback performs heavy computation, parses a huge payload, or creates an endless microtask chain, it still blocks progress.

At staff level, I use this model to decide where work belongs. I keep main-thread callbacks small, move CPU-heavy work to Web Workers or Node worker threads, avoid depending on subtle scheduling races, and measure user or service impact through browser interaction latency, event-loop delay, utilization, and tail latency.”

Fundamentals

Q: Is the event loop part of JavaScript or V8?
No. JavaScript defines language behavior, and V8 executes JavaScript. The browser or Node.js host provides the event loop, timers, I/O APIs, and callback scheduling.

Q: Is the event loop one callback queue?
No. Browsers have task sources, a microtask queue, and rendering opportunities. Node.js has libuv phases plus separate next-tick and V8 microtask queues.

Q: What does run-to-completion mean?
Once JavaScript starts running a task or callback, it continues until it returns or throws. Another callback cannot interrupt the current call stack.

Q: What does blocking the event loop mean?
JavaScript occupies the main thread for too long, delaying every callback, input event, timer, render, or request that needs that thread.

Q: Does async code automatically avoid blocking?
No. Waiting for asynchronous I/O does not block the thread, but synchronous work before or after an await can still block it.

Q: Can a timer interrupt running JavaScript when its delay expires?
No. The delay only marks the earliest time the callback becomes eligible. It runs after the current stack and higher-priority work finish.

Q: What is a microtask?
A high-priority callback, commonly created by Promise reactions, await continuations, and queueMicrotask(). Microtasks are drained at specific checkpoints before ordinary scheduled work continues.

Q: Why can microtasks cause starvation?
If every microtask schedules another microtask, the queue may never empty, preventing rendering or event-loop phase callbacks from running.

Browser event loop

Q: What happens during a simplified browser event-loop turn?

  1. Run one task, such as a click, timer, or script.
  2. Drain the microtask queue.
  3. The browser may update rendering.
  4. Continue to another task.

Q: Why does a Promise callback usually run before setTimeout(..., 0)?
The Promise callback is a microtask. The timeout callback is a future task. The browser drains microtasks before selecting another task.

Q: When does the browser render?
Rendering usually happens between tasks after microtasks have drained, when the browser decides a frame update is needed.

Q: Why can a long task hurt user experience?
The main thread cannot process input or render while the task runs. This increases interaction latency and causes dropped frames.

Q: When should requestAnimationFrame() be used?
For visual work that should run before the next paint, such as animation or frame-aligned Document Object Model (DOM) and canvas updates.

Q: Why is setTimeout() not an animation primitive?
It schedules a task after a minimum delay but is not synchronized with the browser’s rendering cycle.

Q: When should a Web Worker be used?
For CPU-heavy work that does not need direct DOM access, such as parsing, data processing, compression, or syntax highlighting.

Q: Can a Web Worker directly update the DOM?
No. It communicates with the main thread through messages or carefully managed shared memory.

Q: What should be measured for browser event-loop health?
Long tasks, Interaction to Next Paint (INP), Total Blocking Time, frame drops, and main-thread activity.

Node.js event loop

Q: What are the main Node.js event-loop phases?
At a high level: timers, pending callbacks, idle/prepare, poll, check, and close callbacks.

Q: What is the poll phase?
It waits for I/O readiness and runs many I/O callbacks. It is the center of libuv’s I/O processing.

Q: What runs in the check phase?
Callbacks scheduled with setImmediate().

Q: What runs first after a normal Node.js callback returns?
Node drains process.nextTick() callbacks first, then V8 Promise and queueMicrotask() callbacks, before continuing to another phase callback.

Q: Why can process.nextTick() be dangerous?
Recursive next-tick scheduling can starve timers and I/O because Node drains that queue before advancing the event loop.

Q: Does every asynchronous Node.js API use the libuv worker pool?
No. Network I/O usually uses operating-system readiness APIs. File-system operations, dns.lookup(), selected crypto operations, and zlib commonly use the worker pool.

Q: Does the libuv worker pool run JavaScript in parallel?
No. It performs selected native work and reports completion to the main event loop, where the JavaScript callback runs.

Q: Which runs first inside an I/O callback: setImmediate() or setTimeout(..., 0)?
setImmediate() runs first because the loop moves from poll to the check phase. The timer waits for later timer processing.

Q: Which runs first when both are scheduled from the main script?
Do not rely on a fixed order. Startup timing and timer eligibility can affect the result.

Q: Why does a Node.js process stay alive after the main script finishes?
Referenced resources such as servers, sockets, timers, pending requests, or workers still give the event loop work to track.

Q: When should worker_threads be used?
For substantial CPU-bound JavaScript that would otherwise block the main event loop.

Q: How are worker threads different from the libuv worker pool?
Worker threads run JavaScript in separate V8 isolates and event loops. The libuv pool handles selected native operations behind Node APIs.

Q: What should be measured for Node.js event-loop health?
Event-loop delay, event-loop utilization, CPU profiles, tail latency, garbage-collection pauses, and worker-pool pressure.

Browser vs Node.js

BrowserNode.js
Coordinates JavaScript, Web APIs, user input, and renderingCoordinates JavaScript, server I/O, timers, and libuv phases
Has tasks, microtasks, and rendering opportunitiesHas event-loop phases, process.nextTick(), and microtasks
Provides DOM and requestAnimationFrame()Provides process.nextTick() and setImmediate()
Uses Web Workers for off-main-thread JavaScriptUses worker threads for parallel JavaScript
Performance focus: input latency and frame budgetPerformance focus: throughput and request latency

Q: What do browser and Node.js event loops have in common?
Both run JavaScript to completion, defer asynchronous callbacks until the stack is available, process Promise microtasks, and can be blocked by long synchronous work.

Q: What is the most important difference?
The browser must create responsive visual frames. Node.js has no rendering pipeline and instead organizes native I/O through libuv phases.

Q: Is setTimeout() provided by the same system in both environments?
No. It is a host API. Browsers provide it through Web APIs; Node.js provides its own timer implementation integrated with libuv.

Predict the output

Browser or normal JavaScript task

console.log("A");
 
setTimeout(() => console.log("B"), 0);
 
Promise.resolve().then(() => console.log("C"));
 
console.log("D");

Answer: A, D, C, B.

  • A and D run synchronously.
  • C is a microtask.
  • B is a later timer task.

Node.js CommonJS

console.log("A");
 
process.nextTick(() => console.log("B"));
Promise.resolve().then(() => console.log("C"));
 
console.log("D");

Answer: A, D, B, C.

  • The main script finishes first.
  • Node drains process.nextTick().
  • It then drains Promise microtasks.

Node.js inside I/O

const fs = require("node:fs");
 
fs.readFile(__filename, () => {
  setTimeout(() => console.log("timeout"), 0);
  setImmediate(() => console.log("immediate"));
});

Answer: immediate, then timeout.

The I/O callback runs around poll, and the check phase for setImmediate() comes before later timer processing.

Staff-level scenario questions

Q: A browser interaction feels slow even though API latency is low. What would you investigate?
Main-thread long tasks, large synchronous handlers, excessive DOM work, layout thrashing, microtask chains, JavaScript bundle execution, and INP traces.

Q: A Node.js service has low average latency but severe p99 spikes. What event-loop causes would you consider?
CPU-heavy callbacks, synchronous APIs, large JSON operations, pathological regular expressions, garbage collection, excessive next-tick or microtask work, and libuv worker-pool saturation.

Q: How would you fix CPU-heavy work in a browser?
Reduce it, split it into bounded chunks, move it off the interaction path, or use a Web Worker.

Q: How would you fix CPU-heavy work in Node.js?
Optimize or partition it, move substantial work to a worker-thread pool, or move durable workloads to an external job system.

Q: Should increasing UV_THREADPOOL_SIZE be the first response to latency?
No. Measure pool pressure first. More threads can improve throughput for the right workload but add memory and CPU scheduling overhead.

Q: Why should business logic not depend on races between timers and immediates?
Scheduling depends on context and runtime state. Express required ordering directly with control flow, Promises, queues, or explicit state transitions.

Final interview close

The practical value of understanding the event loop is not memorizing queue names. It is knowing which work owns the main thread, which work can wait elsewhere, when callbacks become eligible, and how scheduling choices affect responsiveness, throughput, and tail latency.