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
Comparison Table
| Aspect | Blocking I/O | Non-blocking I/O |
|---|---|---|
| Call behavior | Call halts the calling thread until the operation completes | Call returns immediately, with data or an EWOULDBLOCK/EAGAIN error |
| Thread state while I/O is pending | Thread is suspended off the run queue — no CPU used, but unavailable for other work | Thread stays runnable and can be reused to serve other requests |
| How readiness is discovered | OS wakes the thread automatically once data is ready | Caller polls or registers with select/poll/epoll/kqueue |
| Concurrency model | One thread (or process) per concurrent connection | Single or few threads multiplex many connections via an event loop |
| Resource overhead at scale | Grows linearly with connections — thread stacks, context switches | Stays flat, bounded by CPU cores rather than connection count |
| Code / control-flow complexity | Simple, sequential, top-to-bottom logic | Callback, promise, or async/await structure; state tracked across suspensions |
| Failure / edge-case handling | A slow or hung peer blocks the thread indefinitely without a timeout | A 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.