Hello! In our last lesson, we explored the low-level mechanics of I/O, comparing blocking, non-blocking, and I/O multiplexing. We concluded that modern primitives like epoll and kqueue are the key OS-level tools for efficiently monitoring thousands of network connections.
This lesson connects that low-level knowledge to high-level architecture. Our goal is to explain how modern event-driven servers use I/O multiplexing to handle high concurrency. We'll move up from the system calls themselves to understand the architectural pattern—the event loop—that powers servers like Nginx, Redis, and the runtimes for Node.js and Go. Understanding this is not just academic; it's a core concept that frequently comes up in backend interviews to test your grasp of system performance.
The Event-Driven Architecture
In the previous lesson, we established why the "thread-per-connection" model doesn't scale. The memory overhead and constant context switching associated with thousands of threads create a performance bottleneck, a problem famously known as C10k.
Modern high-concurrency servers solve this with an event-driven architecture. Instead of dedicating a thread to wait on each connection, they use a small, fixed number of single-threaded processes (often called "workers," typically one per CPU core). Each worker process can handle thousands of connections simultaneously.
This architecture has a few key components:
- An I/O multiplexing mechanism (
epoll,kqueue) to monitor many connections at once. - An event loop that acts as the central dispatcher.
- Non-blocking handlers or callbacks that execute the actual logic for a request.
The diagram below provides a high-level overview of this model. Notice how multiple sockets are monitored by the I/O multiplexing component, which then feeds events to a single-threaded event loop.

The Heart of the System: The Event Loop
The "magic" of the event-driven model lies in the event loop. It's a simple yet powerful design pattern. At its core, the event loop is a single thread running a continuous loop that does the following:
- Wait for Events: The loop makes a single, blocking call to the I/O multiplexing API (e.g.,
epoll_wait). It asks the OS, "Are any of the connections I'm watching ready for I/O?". The OS handles the monitoring efficiently, and the worker process's thread sleeps, consuming no CPU. - Process Events: When data arrives or a connection is ready to be written to, the OS wakes up the worker thread. The
epoll_waitcall returns a list of only the connections that are ready. - Dispatch Handlers: The event loop iterates through this short list of ready connections and executes the corresponding non-blocking handler for each one (e.g., read the incoming HTTP request, send the next chunk of a file).
- Repeat: The loop then returns to step 1, waiting for the next batch of events.
Because the handlers are non-blocking and execute very quickly, a single thread can cycle through thousands of connections, giving each one attention exactly when it's needed and wasting no time on idle connections.
This video provides an excellent conceptual breakdown of how a standard protocol, like HTTP, must be refactored to work within an event loop.
what's the difference between processes, threads, and io multiplexing?
This video from a university course explains the shift in thinking required for I/O multiplexing, moving from a sequential program to a state machine driven by events.
First, watch the introduction to I/O multiplexing and the event loop. Then, focus on the example from refactoring an HTTP server. Notice how the process is broken down into discrete states (e.g., "waiting for request," "waiting for file to open," "reading file"), where each step is triggered by an I/O event.
Case Study: Nginx
Nginx is the quintessential example of an event-driven server. It was designed specifically to solve the C10k problem and its architecture perfectly embodies the principles we've discussed.
An Nginx instance runs a single master process and several worker processes. The master process handles configuration and management, while the worker processes do all the heavy lifting. Each worker process is single-threaded and runs its own independent event loop.
The following reading from "The Architecture of Open Source Applications" is a fantastic, in-depth look at Nginx's design.
The Architecture of Open Source Applications (Volume 2) ...
This chapter explains the 'why' and 'how' behind Nginx's high-performance architecture. It's a must-read for any backend engineer.
First, read the section Isn't Apache Suitable? to understand the context and the problem Nginx was designed to solve. Next, read the core explanation in the section "Overview of nginx Architecture". Pay close attention to the paragraphs describing the event-driven architecture. Continue to the "Workers Model" section, reading about how workers execute the run-loop. Finally, in "nginx Internals", read the short list that describes the worker's run-loop, which explicitly mentions processing events with epoll or kqueue.
To reinforce this, the following video provides a great visual walkthrough of the Nginx architecture, focusing on how epoll enables the event loop to function so efficiently.
How NGINX Handles MILLIONS of Requests With Just 1 Process 🔥
This video by ByteMonk animates the concepts from the article you just read, making them easier to visualize.
Start by watching the overview of the Nginx process architecture. Then, watch the detailed explanation of what happens inside a worker process. Finally, pay close attention to the crucial segment explaining how epoll powers the event loop to handle thousands of connections without blocking.
The diagram below neatly summarizes the flow within an Nginx or Redis server, showing how epoll acts as the bridge between client requests and the single-threaded event loop.

The Pattern in Other Systems
This event-driven model is not unique to Nginx. It's the foundation for many high-performance systems you'll encounter.
- Redis: Much like Nginx, Redis uses a single-threaded event loop with I/O multiplexing to handle tens of thousands of client connections with extremely low latency.
- Node.js: The entire Node.js runtime is built around this concept. Its event loop is powered by a C++ library called
libuv, which usesepollon Linux andkqueueon macOS under the hood. This is why you work with asynchronous callbacks, promises, andasync/awaitin Node.js—these are programming constructs designed to work with a non-blocking event loop. - Go: While Go's concurrency model with goroutines feels different, the underlying runtime scheduler uses the same principle. When a goroutine performs a network I/O operation, the scheduler parks the goroutine and registers its file descriptor with a network poller (based on
epoll/kqueue). When the I/O is ready, the scheduler is notified and makes the goroutine runnable again. This gives you the power of event-driven I/O with a simpler, sequential-looking programming model.
The following resource provides a quick tour of how widely this pattern is used.
Asynchronous Non-blocking I/O under the hood: poll, select ...
This article section demonstrates the ubiquity of the event-driven model in modern backend technologies.
Please read the section titled Who uses epoll?. It provides brief code snippets and explanations for Node.js, Go, Nginx, Java NIO, and Redis, showing how each one leverages this powerful OS feature.
Conclusion
You have now connected the dots from low-level OS system calls to the high-level architecture of modern servers. This understanding is what separates a developer who simply uses a framework from an engineer who understands how and why that framework performs under load.
Key Takeaways:
- Modern servers use an event-driven architecture to handle high concurrency, avoiding the scalable limitations of the thread-per-connection model.
- The core of this architecture is the event loop, a single thread that waits for I/O events using an efficient multiplexing mechanism like
epollorkqueue. - The event loop only processes connections that are ready for work, avoiding any wasted CPU time on idle connections.
- Systems like Nginx, Redis, and Node.js are prime examples of this architecture, enabling them to handle thousands of simultaneous connections with minimal CPU and memory resources.
- The Go runtime uses the same underlying principles, but abstracts them behind the goroutine scheduler, providing a different programming model on top of the same efficient I/O handling.
This lesson concludes our first module on Networking and OS Foundations. You've built a solid base, from tracing an HTTPS request to understanding how a server juggles thousands of them. In our next lesson, we will begin Module 2, "RESTful API Design and Communication," where we'll move up the stack to design the APIs that run on these high-performance servers.