serversim
LearnSimulatorTemplatesPricing
…
…
Open menu
Learn↗Simulator↗Templates↗Pricing↗
My DocumentsDocs
Foundations contents/8 / 86min

When does an event loop win, and when does it lose?

The trapIf handling many jobs from few seats always wins, why do plenty of servers still run hundreds of seats?

Move it yourself

① When the work is mostly waiting — 1 seat (event loop) vs 4 seats (thread pool)

0/49 stepSpeedseats (threads)
0.0sdone01DBvaultqueue 0
servingon the phoneat the vaultfree

0.0s · queue 0 · serving 0 · done 0 — nobody has arrived yet

② One heavy computation joins — the loop cannot call anyone until it finishes

0/42 stepSpeedseats (threads)
0.0sdone01DBvaultqueue 0
servingon the phoneat the vaultfree

0.0s · queue 0 · serving 0 · done 0 — nobody has arrived yet

③ Sitting and waiting for the answer instead of giving the seat back (a blocking call) — back to a one-seat synchronous server

0/49 stepSpeed
0.0sdone01DBvaultqueue 0
servingon the phoneat the vaultfree

0.0s · queue 0 · serving 0 · done 0 — nobody has arrived yet

Follow the sentences

1/49

When does an event loop win, and when does it lose?

Text mode advances to the next sentence every 3 seconds.

Picture it this way

What the last chapter called "two hundred windows" this chapter calls seats — the counter here is the one CPU that actually does the work. Picture a bank with exactly one seat (one thread). Every customer has to fetch paperwork from the vault, and the teller only calls whoever has just come back. Waiting frees the counter, so a single seat still keeps many jobs going at once — that is the "1 seat" board above, and that is what an event loop is.

Now the same work in a bank with four seats. There is still one counter, and the four seated customers share the same set of jobs. Both boards finish the work; what differs is how many seats were held.

On the second board a heavy computation joins the queue. In the one-seat bank the computing customer never leaves the counter, so everyone back from the vault just stands and waits. With several seats the counter is time-sliced between them and the rest keep moving.

The third board is the one-seat bank where the teller waits at the empty counter until the customer returns from the vault — and nothing overlaps any more. Sitting there with your hands off the work until the answer arrives is called blocking, or synchronous; giving the seat back and picking the job up when the bell rings is non-blocking, or asynchronous — in Java these two go by the names IO and NIO.

Now the numbers

measured · computed just now

We ran 12 mostly-waiting jobs through 1 seat and through 4 seats. One seat took 24.0 ticks; 4 seats took 34.6 — four times the seats and 1.4 times slower.

Where do those 10.6 ticks come from? With 4 seats only 4 customers can be at the vault at once, while the single-seat teller had 7 of them there and kept taking the next one. Re-run the same pool with the cost of changing seats set to zero and it takes 31.6 ticks — so the 21 changes of who sits at the counter (0 for one seat) account for only 28.3% of the gap. The rest is how many waits were kept in flight at once.

So the win is not speed; it is holding many waits open from few seats. In this model the seats held at once were 4 against 1.1 (a factor of 3.5) — a simplification that counts one seat as 1 and one job parked at the vault as 0.02 — while the counter spent 69.4% and 100% of the time actually working. Whether real memory drops too is something to check in the preset below: it does not always.

Now add one heavy computation. The whole batch finishes sooner on one seat (40.5 ticks) than on 12 seats (46.8). Yet the average finishing time of the other 12 requests is 38.1 ticks on one seat against 17.6 on 12 — a gap of 2.2x. Total throughput and what one customer waits are different questions.

Finally, make a blocking call inside the loop: the same 12 jobs take 96.0 ticks — tick for tick what a one-seat synchronous server takes (96.0). The number of people at the vault at once fell from 7 to 1, so there was nothing left to overlap.

These numbers are not written into the sentence. They come from a simulation run while this page rendered.

So which one do you choose

SituationChooseWhy
Mostly waiting, many connections (external APIs, databases, sockets)Event loop, or coroutines (the same model, written so the code still reads top to bottom)Waiting frees the counter, so one seat can keep many jobs waiting at once. The win is holding many connections from few seats — not a guarantee that memory drops — board ① above.
One request holds the CPU for a long time (image processing, crypto, large sorts)Thread poolA computation never frees the counter. With several seats the OS time-slices between them so the rest do not stall — board ② above. Sizing the pool near the core count is a common rule of thumb, not something measured here.
You must use a library that cannot let go while it waits (an old database driver, say)A thread pool, or offload just that call to a separate seatA blocking call inside the loop leaves nothing to overlap: you are back to a one-seat synchronous server — board ③ above.
Huge numbers of connections, almost all of them idle (WebSocket, SSE, long polling)Event loopConnection count stops being tied to seat count. Give every connection its own seat and you run out of seats long before you run out of work.

Try it in the simulator

  1. 1.Select WebFlux in the 'reactive stack' preset and find ioWorkerCount. There is no Tomcat comparison card here. Distinguish the seat comparison in the illustration above from this preset.
  2. 2.Keep the load, seed and run duration fixed. Record the default result, then lower only ioWorkerCount. Reset and run for the same duration to compare CPU, throughput and latency.
  3. 3.Many requests can overlap their waits, but computation still needs workers. The benefit of adding workers depends on the work; comparisons across frameworks also change request costs and settings.
  • Both stacks report a 100% success rate — now look at memory too: this preset measures 352MB (WebFlux) against 324MB (Tomcat), so WebFlux actually uses slightly more. The win of an event loop is concurrent-connection capacity (maxConcurrent 10,000), not per-thread stacks. The CPU split of 18% against 46% in the step above points the same way, but that figure is what the simulator produced for this one preset, and it moves with the stack and the workload.
  • Open "WebSocket chat — 10k concurrent users" from the preset list as well: ten thousand people are connected and WebFlux sits at 6% CPU. A model that holds a thread per connection would need ten thousand threads for that, which is an out-of-memory crash. That is the last row of the table above.
Open it in the simulator →

In one sentence

Holding many waits from few seats is the better choice when the work is mostly waiting, and the worse one the moment a long computation or a blocking call joins the queue.

Progress is stored in this browser only (no account, no sync between devices).

Quick check

0 / 3 correct
  1. 1.On a server that interleaves many jobs on one seat, which request is most dangerous?

  2. 2.What is the main reason 1 seat (event loop) finished the same work sooner than 4 seats (thread pool)?

  3. 3.What happens if you use a synchronous (blocking) database driver inside an event loop?

New words here

event loop
A single seat that keeps asking "anything ready?" and handles each ready job in a short burst. Waiting jobs give the seat back, so one seat can keep many in flight.
thread pool
A set of seats made up front and handed out to requests. Each seat costs memory, and every change of who sits there costs time.
coroutines
The same scheduling as an event loop, written so the code still reads top to bottom ("wait here, then continue") — Python asyncio, or Kotlin coroutines on a single thread. A different syntax, not a different model. Goroutines in Go are the exception: they are spread over several cores and a long computation is forcibly moved aside, so trap ② does not carry over to them.

Previous

Two hundred windows — why are only ten working?

serversim

Learn why servers slow down and fail, then reproduce the same mechanism in a simulator.

© 2025-2026 serversim · Architecture simulation tool

Product

  • Learn
  • Simulator
  • Templates
  • Pricing

Resources

  • Docs
  • Benchmarks
  • Blog

Legal

  • Privacy
  • Terms
  • Refund