Overview

Blocking and non-blocking I/O differ in what happens to the calling thread when a read or write can’t complete immediately. Blocking I/O suspends the thread until data is ready; non-blocking I/O returns at once and lets the caller check back later. The choice shapes how a system scales to many simultaneous connections.

Comparison Diagram

Blocking I/ONon-blocking I/OThreadcall read()thread blockedno other work possibleuntil data arrivesdata ready, resumes1 thread ≈ 1 in-flight callthread / event loopread() returns at oncepoll / epoll checkretrymeanwhile: serves otherconnectionsready → callback fires1 thread ≈ many in-flight calls

Comparison Table

AspectBlocking I/ONon-blocking I/O
Call behaviorCall halts the calling thread until the operation completesCall returns immediately, with data or an EWOULDBLOCK/EAGAIN error
Thread state while I/O is pendingThread is suspended off the run queue — no CPU used, but unavailable for other workThread stays runnable and can be reused to serve other requests
How readiness is discoveredOS wakes the thread automatically once data is readyCaller polls or registers with select/poll/epoll/kqueue
Concurrency modelOne thread (or process) per concurrent connectionSingle or few threads multiplex many connections via an event loop
Resource overhead at scaleGrows linearly with connections — thread stacks, context switchesStays flat, bounded by CPU cores rather than connection count
Code / control-flow complexitySimple, sequential, top-to-bottom logicCallback, promise, or async/await structure; state tracked across suspensions
Failure / edge-case handlingA slow or hung peer blocks the thread indefinitely without a timeoutA slow peer only delays its own event; a stalled callback can starve the whole loop

Key Differences

  • Blocking I/O ties up a thread for the full call; non-blocking frees it immediately.
  • Non-blocking servers rely on an event loop to learn when data is finally ready.
  • Blocking scales concurrency with more threads; non-blocking scales with more callbacks on fewer threads.
  • Blocking code reads sequentially; non-blocking code needs explicit state management across suspensions.
  • At high connection counts, blocking hits a C10K wall that non-blocking avoids.

When to Use Each

Blocking I/O

  • Simple Scripts and CLI Tools: Sequential, top-to-bottom blocking calls keep code easy to read and debug when high concurrency isn’t a concern.
  • Low-Concurrency Services: With only a handful of connections active, the one-thread-per-connection model never reaches the resource overhead that scales linearly with connections.
  • Straightforward Failure Handling: Without callback or promise state to track, errors surface in a linear call stack that’s simpler to trace than a suspended async chain.

Non-blocking I/O

  • High-Concurrency Servers: Proxies, chat backends, and API gateways handling thousands of simultaneous connections need an event loop to avoid the C10K wall blocking I/O hits.
  • Resource-Bounded Scaling: Since overhead stays flat and bounded by CPU cores rather than connection count, non-blocking I/O suits systems where thread-per-connection costs would be prohibitive.
  • Multiplexing Many Slow Clients: A single event loop can serve many peers at once, so one slow connection only delays its own event instead of tying up an entire thread.