OS & Systems FundamentalsI/O Models (Blocking, Non-blocking, Async)Medium⏱️ ~3 min

Blocking vs Non-Blocking I/O: Memory and Threading Trade-offs

Core Distinction
Blocking I/O suspends the calling thread until the operation completes. Non-blocking I/O returns immediately, reporting whether data is ready or not. The thread does not wait.

Blocking I/O Model

When you call read() on a blocking socket, the kernel puts your thread to sleep until data arrives. The thread cannot do other work. With one thread per connection, 10,000 connections need 10,000 threads. Each thread consumes 1 MB stack space: 10 GB just for stacks.

Blocking is simple to program. Code reads linearly: call read, get data, process, repeat. Error handling is straightforward. But thread overhead limits scalability. Context switching 10,000 threads destroys performance even before memory exhaustion.

Non-blocking I/O Model

Non-blocking sockets return EAGAIN if no data is ready. The caller must try again later. This enables one thread to manage many connections by checking each in turn. If a connection has data, process it. If not, move to the next connection.

Raw polling is inefficient. Checking 10,000 connections in a loop wastes CPU. Event notification systems solve this: epoll, kqueue, and IOCP tell you which connections are ready. Check only ready connections, handle them, repeat. One thread can manage hundreds of thousands of connections.

Threading Implications

Blocking I/O requires many threads for concurrency. Non-blocking I/O enables few threads to handle many connections. The trade-off is complexity: non-blocking code must handle partial data, manage state machines, and coordinate event driven callbacks.

💡 Key Insight: Blocking I/O trades memory (threads) for simplicity. Non-blocking I/O trades complexity for scalability. For high connection counts, non-blocking with event loops is the only viable option.
💡 Key Takeaways
Blocking I/O suspends thread until complete; non-blocking returns immediately with EAGAIN if not ready
Thread per connection: 10K connections need 10K threads, 10 GB stack memory
Non-blocking with epoll or kqueue: one thread handles hundreds of thousands of connections
Blocking is simpler to code; non-blocking requires state machines and event handling
High concurrency requires non-blocking; low concurrency can use blocking for simplicity
📌 Interview Tips
1Calculate thread memory: 10K connections with 1 MB stack each equals 10 GB just for stacks, ignoring heap
2Explain why raw polling is wasteful: checking 10K sockets in a loop burns CPU checking mostly not-ready sockets
3When discussing scalability, mention that event loops with epoll handle 100K plus connections on one thread
← Back to I/O Models (Blocking, Non-blocking, Async) Overview