Interview Prep Zoneby CuriouserLabs

Question 4 of 5 · staff level

Blocking vs non-blocking I/O, and the road from select to epoll to io_uring — why did each step happen?

🎤 Say this first

The problem is never CPU — a blocked thread is free — it's threads: 10k connections × 1 thread each was unaffordable (C10K), so we needed one thread watching many FDs. select/poll hand the kernel the whole FD list every call — O(n) per wakeup, fine for dozens, death at thousands. epoll registers interest once (epoll_ctl), then epoll_wait returns only ready FDs — O(ready): the engine of Netty, nginx, Node. io_uring goes further: submit operations (not readiness queries) into a shared-memory ring, harvest completions with near-zero syscalls — true async for files too (epoll never worked for disk I/O). Meanwhile virtual threads attack the same problem from the other end: make threads cheap so blocking style is fine again.

The full picture

  • Readiness vs completion — the taxonomy that organizes everything: epoll says 'FD is ready, now you do the read' (reactor pattern); io_uring/Windows IOCP say 'your read has finished, here's the data' (proactor). Files are never 'ready' in a useful sense — that's precisely why epoll can't do disk I/O and io_uring can.
  • Edge vs level triggering: level = keep telling me while readable (forgiving); edge = tell me once on transition (fewer wakeups, but you must drain to EAGAIN or hang forever — the classic Netty/epoll-ET bug class).
  • The thundering herd: many threads waiting on one socket, all woken per event, one wins — wasted switches; fixed by EPOLLEXCLUSIVE / SO_REUSEPORT sharding (nginx's accept_mutex story).
  • Where it lands for a JVM engineer: Netty/event loops = epoll + reactor + you must never block the loop; virtual threads = keep blocking code, JVM parks/unmounts on readiness underneath. Same kernel machinery, opposite programming models — and io_uring adoption (Netty incubator, databases) is the current frontier.

🔄 Likely follow-up questions

  • Why exactly can't epoll express 'this file read completed'?
  • What must you guarantee in edge-triggered mode to avoid stalls?
  • Do virtual threads make epoll-based frameworks obsolete — where does each still win?