They were scripts, not people.
No room of 10 240 people has played klapp. A scripted client joins, waits and answers once. It does not switch apps, lose signal, reload the page or sit on a phone five years old.
Research · published 19 September 2026 · our own measurement
On 13 September 2026 we put 10 240 synthetic players into one game on the live klapp server, which runs on half a CPU core. All 10 240 answered the same question at the same moment, and all 10 240 answers were registered. None was lost. The server used 65% of its half core. The limit we ran into was in our load generator, not the server. This page gives the setup, every run that day, and what the numbers do not show.
Players in one game
10 240
Ten load generators, 1024 clients each.
Answers registered
10 240
Each confirmed back to the client that sent it.
Answers lost
0
And 0 clients left without a reply.
CPU of the half core
65%
0.325 of the 0.5 cores allocated, and 259 MB.
Synthetic clients, not people. Each client was a scripted WebSocket connection. It opened a connection to the production address, joined one game with its six-digit code, and waited for the server's welcome. When the host started the round, the server announced when the answer window would open; every client waited for that moment and then sent one answer. It then waited for the server's own reply saying which answer it had recorded for that client. An answer counted as registered only when that reply came back with the answer in it. Lost means the reply came back empty. A client that never got a reply at all was counted separately; in the largest run that number was also zero.
The rule was strict on purpose. A known complaint about live quiz tools is an answer that does not register when a player taps. “Almost everyone got through” would not have passed. Every client in the room had to answer inside the window, and every answer had to come back confirmed.
The server was the production service at playklapp.com, in Azure's Norway East region:
one container with 0.5 of a CPU core allocated, one process, every game held in its memory. There is no
participant limit in the code. The largest run was against the build of commit f427ff0; the
result was written into our records the same evening, in pull request #84.
The load generators were containers in the same Azure region, each with its own CPU and network namespace, all connecting through the public address and its TLS termination, the same way a phone does. The largest run used ten of them, each asked to open 1200 clients.
Every run that day, in order
13 September 2026, against production. CPU is the share of the half core
| Run | Asked for | Got in | Lost | Connection, median | CPU | Memory |
|---|---|---|---|---|---|---|
| 2000, 5 × 400 | 2000 | 2000 | 0 | 18–26 ms | 48% | 157 MB |
| 5000, 5 × 1000 (before the fix) | 5000 | 4821 | 0 | 71–365 ms | 84% | not recorded |
| 5000, 5 × 1000 (after the fix) | 5000 | 5000 | 0 | 14–17 ms | 41% | 215 MB |
| 10 240, 10 × 1024 | 10 240 | 10 240 | 0 | 20–26 ms | 65% | 259 MB |
Connection time in the largest run, from opening the connection to the welcome
| Measure | 10 240 clients |
|---|---|
| Median (p50) | 20–26 ms |
| 95th percentile (p95) | 214–234 ms |
| Slowest | about 700 ms |
A range is the spread between load generators, not between players: the figure is measured per generator, and ten generators give ten medians.
The largest run in full: 10 240 clients in, 10 240 answers registered, 0 lost, 0 without a reply, and the server turned no one away. Connection time was 20–26 ms at the median, 214–234 ms at the 95th percentile, and about 700 ms at the slowest. The server used 0.325 of its 0.5 cores and 259 MB of memory.
The 179 missing in the second run failed during the TLS handshake inside the load generator. The server turned none of them away, and all 4821 who got in had their answers registered. That run was before the fix below; after it, all 5000 got in.
The fix between the second and third run came from profiling the server with 1000 players on one machine. Most of its work was sending: every message to the room was turned into text once per recipient, and the full list of player names went to every phone. After the change a message is encoded once for the whole room, and phones get a player count instead of the list. With about the same number of players, CPU fell from 84% to 41%, and the median connection time from 71–365 ms to 14–17 ms.
Each load generator stopped at exactly 1024 connections, however many it was asked to open. That is why the largest run needed ten of them, and why it ended at 10 × 1024 = 10 240 and not 12 000. The obvious explanation, the container's limit on open files, was ruled out in the same run: the limit was set far above 1024, and the stop stayed at 1024 when we lowered the limit to 65 535. Our remaining guess is a cap on outgoing ports per container in Azure's network. We have not confirmed it.
It was the second time the measuring tool was the ceiling. The day before, from one machine, every run stopped at 570–690 connections whatever the ramp. 1000 clients from one process looked like a server limit, with 51.9% of answers registered; the same 1000 split over two processes got 100% for all 573 that came in. Moving to separate load generators took the median connection time from 3600 ms with 500 clients to 18–26 ms with 2000.
How CPU grew. With the current code there are two points: 5000 clients used 41% of the half core and 10 240 used 65%. That is about 24 percentage points for 5240 more players. The two earlier points (48% at 2000, 84% at 4821) were measured on the code before the fix and do not belong on the same line. We have not measured where the server stops, and a straight line through two points does not say. What sets the limit today is the half-core allocation, which is a setting, not the design of the server.
Where each ceiling actually sat
No room of 10 240 people has played klapp. A scripted client joins, waits and answers once. It does not switch apps, lose signal, reload the page or sit on a phone five years old.
The load generators ran in the same Azure region as the server, over Azure's own network, and may have shared outgoing addresses. Real players come over mobile networks, school and office Wi-Fi, with more latency and more dropped connections. That 10 240 phones on 10 240 different networks behave the same way is not shown.
klapp now has a fallback: a player whose network blocks WebSocket gets a plain HTTPS stream from the server (server-sent events) with answers sent as ordinary requests. That fallback has tests, but it has not been load-tested. How many players it holds at once is unknown.
All clients were in the same game and answered one question. Many smaller games at once, or a full game of many rounds, share the same process but were not the test.
Rooms live in the memory of one running copy of the server. Since 8 October a quiz room is also saved to storage, so when we release a new version the next copy picks the game up again after a short wait. Q&A and polls that are running are not saved: a release ends them. A large event on a day we release could lose its Q&A.
Every change since then has passed a smaller capacity check: 2000 clients on one machine must all have their answers registered before a release goes out. That check is a floor, not a repeat of this measurement.
Run the load from more than one machine from the start; one machine will measure itself. Give each client its own connection, have every client answer at the moment the window opens, and count an answer as registered only when the server confirms it back to that client. Record the server's CPU as a share of what it is allocated, and the connection time per load generator. When a limit appears, check whether it sits in the generator before calling it the server's.
Ask at hello@playklapp.com if you want the script or the raw output.
A stronger test would add what ours lacks
Where this matters in the product: the quiz page → no participant limit. Also on this site: why colour never carries meaning.