I/O multiplexing allows a single thread to monitor multiple file descriptors (sockets, pipes, files) and react when any of them becomes ready for I/O. Essential for building high-concurrency network servers.
The Problem
Traditional blocking I/O: one thread per connection. With 10,000 connections, you need 10,000 threads — too many (stack memory, context switch overhead).
I/O multiplexing: one thread monitors all 10,000 connections and processes only the ones that are ready.
Three Generations
| System call | Complexity | Max FDs | Efficiency at scale |
|---|---|---|---|
select() | O(N) — scan all FDs | 1024 (FD_SETSIZE) | Poor |
poll() | O(N) — scan all FDs | Unlimited | Poor |
epoll (Linux) | O(1) — only ready FDs | Unlimited | Excellent |
select()
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
select(sock + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(sock, &readfds)) { /* ready */ }
Problems: (1) Limited to 1024 FDs. (2) Bitmask must be rebuilt every call. (3) Scans all FDs even if only one is ready.
poll()
Uses an array of pollfd structs — no hardcoded limit. But still O(N) — the kernel must walk the entire array.
epoll (Linux)
epoll is event-driven, not scan-based. Three system calls:
epoll_create1()— create an epoll instanceepoll_ctl()— register interest in specific FDsepoll_wait()— block until any registered FD is ready
Only ready FDs are returned — O(number of ready FDs), not O(total FDs). This makes epoll scale to hundreds of thousands of connections.
kqueue (BSD/macOS), IOCP (Windows)
Equivalent event-driven mechanisms on other platforms.
Q: What problem does I/O multiplexing solve?
A: Without it, you need one thread per connection. A web server with 10,000 connections would need 10,000 threads — memory and context-switch overhead is prohibitive. Multiplexing lets one thread handle 10,000 connections by only processing those that are ready.
Q: Why is select() limited to 1024 FDs?
A: The fd_set bitmask uses a fixed-size array (typically 1024 bits). The FD_SETSIZE constant defines this limit. poll() removes this by using a dynamically-sized struct array instead.
Q: How does epoll achieve O(1) performance?
A: epoll uses a callback mechanism. When an FD is registered, the kernel installs a callback in the socket’s wait queue. When the socket becomes ready (data arrives), the callback adds the FD to a ready list. epoll_wait() just returns the ready list — no scanning needed.
Q: When would you still use select() or poll()?
A: On platforms that don’t support epoll/kqueue, for simple tools with few FDs, or for portability. libuv and libevent abstract these details — they select the best available mechanism per platform.
Premium Content
Unlock I/O Multiplexing — select, poll, epoll and all premium lessons with a subscription.
From ₹199.99/year — See plans