How can backend servers evolve from:
“1 thread handles everything ”
to threadpools, Hystrix, bulkheads, circuit breakers, async IO and event loops…
Let’s build this story from scratch 🧵
First: what even is a PROCESS?
A process is just a running instance of a program.
A process contains:
memory, files, sockets and threads.
Think of it like an isolated execution container.
Now inside a process comes THREADS.
Thread = actual unit executing code.
Without threads, your program literally does nothing.
In the beginning, let our server handle requests using ONE THREAD.
Request comes → thread processes → response sent.
Easy.
Until second user arrives.
Now the problem starts.
If one request becomes slow, entire server waits. Ex: DB query taking 5 sec.
Meanwhile all other users are blocked too. One slow request freezes the whole server.
"Fine, we'll just add more threads.”
So THREAD PER REQUEST model is born. New request? Spawn new thread. Now requests run concurrently.
Feels amazing initially.
Traffic increases.
10 users. 100 users. 1000 users.
The server is now creating thousands of threads. Then new problems appear. Threads are NOT free.
Every thread needs:
- stack memory
- scheduling
- CPU coordination
- kernel management
Creating and destroying threads repeatedly is expensive too.
Too many threads = massive CONTEXT SWITCHING. CPU spends more time switching threads than doing work.
Also most backend threads aren’t computing. They’re WAITING.
Waiting for:
- DB
- APIs
- disk
- network
Meaning: thousands of expensive threads sitting idle.
We realized, “Creating threads per request is cursed.”
So now THREADPOOLS are introduced. Instead of infinite threads, create a fixed size pool: for ex. 200 threads.
Requests borrow thread → finish work → return it back. Much more efficient.
System survives nicely now. Until…
traffic spike slow downstream service.
Imagine: thread pool size = 200
Normally request takes : 50ms.
But DB suddenly slows: 5 seconds.
Now every request thread becomes occupied much longer. Very quickly, all 200 threads get blocked.
Now no free worker threads exist.
New requests either wait in queue or get rejected instantly...
Then:
timeouts,
5xx errors,
angry users,
oncall engineer crying at 3 AM.
This is THREADPOOL EXHAUSTION.
Then we make things worse accidentally: RETRIES.
Request failed? “Retry bro.”
Now traffic multiplies during outage.
One slow DB becomes full infrastructure apocalypse.
Another realization :
“Why should ONE bad service kill ENTIRE app?”
Ex: Recommendation service is slow. Why should payments, auth and orders also die?
Netflix engineers solved this using HYSTRIX.
Main idea:
SEPARATE THREADPOOLS PER DEPENDENCY/COMMAND.
Example:
payments → 20 threads
recommendations → 10 threads
Now failures stay isolated.
This concept is called BULKHEAD PATTERN. Inspired from ships. If one compartment floods, entire ship shouldn’t sink.
Same idea, contain failures locally.
But even separate pools can fill up.
So another idea came:
CIRCUIT BREAKER.
If service keeps failing, STOP calling it temporarily.
Circuit breaker basically says:
“bro dependency is cooked”.
Instead of:
- waiting forever
- blocking threads
- retry storms
Requests fail FAST. After some time, the system allows a few test requests again. If dependency recovered, circuit closes. If not, it stays open.
System survives.
Then we realized another thing:
“Why waste one thread waiting for network calls?”
This led to:
ASYNC IO EVENT LOOPS.
Instead of 1 thread per connection,
Event loop handles thousands of sockets efficiently.
Core idea behind:
- Node.js
- Netty
- Nginx
- modern high-scale networking
So backend evolution basically became:
1 thread
→ many threads
→ thread explosion
→ threadpools
→ threadpool exhaustion
→ isolated pools
→ circuit breakers
→ async IO
→ event loops
Share and repost if you like this way of learning concepts 🫶
#systemdesign #threadpool #swe