- Paradigm overview: Backend computing evolved from single-threaded request handling to event-driven, non-blocking models. Each paradigm addresses different trade-offs in CPU utilization, memory footprint, and development complexity.
- Thread-based model: Each incoming request spawns a separate OS thread. This approach is simple to implement but limited by thread overhead and context-switching costs at high concurrency. Typical thread pools cap at a few thousand active threads.
- Event-driven model: A single thread processes events using a loop, dispatching work to callback functions. Node.js popularized this model, achieving high throughput for I/O-bound workloads without thread-per-request overhead.
- Reactive programming: Builds on event-driven ideas with declarative data flow and backpressure support. Frameworks like Spring WebFlux and Project Reactor use the Reactive Streams specification to handle asynchronous streams of data.
- Process-per-request model: Common in earlier CGI-based applications, each request forks a new process. This isolates failures but incurs high startup costs. Modern serverless functions follow a similar pattern with cold starts.
- Hybrid approaches: Many production systems combine paradigms. For example, a thread pool handles CPU-bound tasks, while an event loop processes I/O. Kubernetes pods run multiple worker processes, each with its own event loop.
- Language and runtime impact: Java uses native threads mapped to OS threads; Python has a global interpreter lock limiting parallelism; Go provides goroutines with a small memory footprint. Choosing the right runtime depends on workload characteristics.
- Real-world example: A typical REST API server might use a thread pool (e.g., Tomcat with 200 threads) combined with an event-driven non-blocking I/O library (Netty) to serve thousands of concurrent requests without exhausting memory.
- Actor model: Each actor is an independent unit of computation with its own state, communicating via message passing. Systems like Akka and Erlang implement this model, providing fault isolation and location transparency. Actors excel in distributed systems where state encapsulation and failure recovery are critical.
- Coroutine-based model: Languages like Kotlin and C++ support stackful coroutines that suspend at I/O points without blocking threads. This enables asynchronous code written in a sequential style, reducing callback complexity. Coroutines combine the simplicity of thread-based programming with the efficiency of event-driven models.
- Work-stealing scheduler: Modern runtimes like Go and Java's ForkJoinPool use a work-stealing approach where idle worker threads steal tasks from busy queues. This balances load dynamically, minimizing idle time and improving CPU utilization for both CPU-bound and I/O-bound tasks within the same pool.
const http = require('http');
const server = http.createServer((req, res) => {
// Event-driven: single thread handles all requests
setTimeout(() => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}, 100);
});
server.listen(3000);This Node.js example demonstrates the event-driven paradigm: a single thread accepts all connections and uses callbacks (here, a timer callback) to handle each request asynchronously. No thread is blocked during the 100ms delay, allowing the server to process other requests concurrently.
- Backend paradigms include thread-based, event-driven, reactive, process-per-request, and hybrid models.
- Thread-based models are simple but limited by OS thread overhead and context switching.
- Event-driven models achieve high throughput for I/O-bound workloads using a single event loop.
- Reactive programming extends event-driven ideas with declarative composition and backpressure support.
- Language runtimes (Java, Python, Go) impose different concurrency characteristics.
- Production systems often combine paradigms to optimize for mixed workload types.