The Runtime Theory
mediumApplicationDSA#event-loop#concurrency#io

Explain the event loop and why blocking it matters

Tests whether you understand single-threaded concurrency: the event loop as a task queue, I/O as the only way to get concurrency, and what a blocked loop does to every other task.

The Runtime Theory Team2 min readasked at netflix · cloudflare · datadog

This question tests whether you understand concurrency without threads. The interviewer wants: the event loop is a task queue plus a dispatcher, and blocking it pauses everything on that thread — not just your function.

The mental model: a single thread runs a loop that repeatedly does two things: take the next task from a queue, and run it to completion. A "task" is a callback: an I/O completion, a timer firing, a setTimeout handler, a promise continuation. The key property: while one task runs, no other task runs. Concurrency comes from never waiting: when a task starts an I/O operation (socket read, file read, database query), the runtime registers interest in the event source and returns control to the loop immediately. The OS or kernel notifies the runtime (epoll/kqueue/IOCP), which enqueues a new task. So the loop is only busy when there is work to do.

The cost model is the important part. Since tasks run to completion, a task that takes 50 ms delays every queued task by 50 ms. A task that blocks on a synchronous read doesn't just delay — it parks the whole loop: no timers, no other I/O completions, no rendering or request handling. At an interview level, name the practical failure: one accidental synchronous file read in a request handler, and every other user's latency spikes by the duration of that read.

Why blocking matters concretely:

  • Timers are promises, not guarantees. A setTimeout(…, 100) fires 100 ms from now if the loop is free; if the loop is blocked for 200 ms, it fires at ~300 ms.
  • Queue starvation: a tight synchronous loop never yields, so the I/O queue grows without bound — the classic "server is unresponsive but CPU is 100%" incident.
  • Backpressure disappears: the runtime keeps accepting connections and enqueuing work; memory grows.

Tradeoffs and edge cases worth naming:

  • Microtasks vs macrotasks: promise continuations run in the microtask queue drained after the current task, before the next macrotask — which is why await can starve I/O if you never yield.
  • Worker threads/thread pools exist precisely to move blocking work (CPU-heavy or truly synchronous I/O) off the loop thread; the runtime transfers the result back as an event.
  • Libuv/Node, browser engines, and .NET's async I/O all use the same substrate: non-blocking syscalls + completion notification + a task dispatcher.
  • Synchronous-looking async: async/await is still callbacks; the loop doesn't spin per await, it suspends the function's continuation as a task.

A strong closing: "Blocking the event loop is a latency multiplier for everyone else: one slow task delays every task, because the loop is the only thread that matters."

This answer walks

Follow-ups they'll push on

  1. 01What's the difference between microtasks and macrotasks?
  2. 02How would you offload CPU-heavy work without blocking the loop?
  3. 03Why is synchronous I/O on the event loop a bug, not a style choice?
  4. 04How do async/await and promises interact with the loop?

More interviews in this topic

One dispatch a week

The trace behind each question, the tradeoff that explains it, and one technical dispatch per week — no noise.

One technical dispatch per week. No noise.