Polling
Understand why polling for state changes wastes resources and adds latency, when to replace it with webhooks, Server-Sent Events, or WebSockets, and how to evaluate the trade-offs.
Introduction
Polling is a pull-based way to learn whether state has changed: the client asks on a schedule, and the server answers with the current state. The anti-pattern is using a short, fixed interval when changes are infrequent or when many clients are watching the same kind of state.
Mental model: every poll buys a chance to observe a change, but it also consumes request, connection, serialization, and storage capacity. Push or long-polling mechanisms spend that capacity closer to the moment a change actually occurs.
TL;DR
- Polling asks "did anything change?" on a schedule. At scale, most polls return "no," burning compute, connections, and network bandwidth for nothing.
- 10,000 clients polling every 5 seconds generate 2,000 requests per second. If each client's state changes once per 60 seconds, about 91.6% of those polls return no new data.
- For near-real-time updates, compare webhooks (the server calls an external endpoint), Server-Sent Events (a one-way stream over HTTP), WebSockets (full-duplex communication), and long polling.
- Polling is acceptable when the update frequency is low, the latency target is relaxed, or the client cannot receive pushes. The interval should follow the workload rather than habit.
What It Is
It's Monday morning. Your job processing platform launched last week. Users submit async jobs and want to know when they're done, so your frontend polls GET /jobs/{id}/status every 2 seconds until the job completes.
Jobs typically take 30 to 120 seconds. In that window, here's what happens:
- 10,000 concurrent users = 5,000 status requests per second
- Each request hits your database to read the status field
- 98% of them return "RUNNING" (the same response as the last poll)
- You're load-testing your database with 5,000 reads/second to deliver no new information
Scale this to 100,000 concurrent users and your status endpoint serves 50,000 requests/second, all reading the same "still running" value from the same rows. You've built an in-house DDoS against your own database.
I saw this exact scenario take down a production Postgres instance. The status table had no read replica, no caching layer, and 50,000 identical SELECT queries per second. The database CPU hit 100%, which slowed actual job processing, which made jobs take longer, which meant more polling. A death spiral.
Before and After
Before, the client sends GET /jobs/{id}/status every 2 seconds and receives the same RUNNING response until the job changes. After, the client opens an SSE stream or long-polling request and the server waits for a version change before sending an update. The result is still observable job state, but the request rate is tied more closely to changes rather than to the number of clients multiplied by a fixed timer.
The Wasteful Math
The economics of polling are brutal at scale:
Polling interval: 5 seconds
Active clients: 10,000
Requests per second: 10,000 / 5 = 2,000 req/s
Expected update frequency: 1 update per 60 seconds per client
Useful requests per second: 10,000 / 60 β 167 req/s
Waste ratio: (2,000 - 167) / 2,000 = 91.6% of all requests return no new data
The faster the interval, the higher the waste. The lower the update frequency, the higher the waste.
| Interval | Clients | Requests/sec | Useful/sec | Waste |
|---|---|---|---|---|
| 2s | 10,000 | 5,000 | 167 | 96.7% |
| 5s | 10,000 | 2,000 | 167 | 91.6% |
| 10s | 10,000 | 1,000 | 167 | 83.3% |
| 2s | 100,000 | 50,000 | 1,667 | 96.7% |
At 50,000 requests/second, you're paying for infrastructure to serve responses that nobody needs. Each request consumes a TCP connection, a thread (or event loop tick), a database read, serialization, and network bandwidth. Multiply by 96.7% waste and you understand why polling doesn't scale.
Why It Happens
Polling is the simplest possible notification mechanism, and simplicity is seductive.
"It works in development." With 5 test users, polling every 2 seconds is 2.5 requests/second. Your laptop handles that without blinking. The problem only surfaces at 10,000+ concurrent users, which you never test locally.
"We don't control the client." If you're building a public API, you can't force clients to implement webhooks or SSE. Polling is the lowest-common-denominator approach that works from any HTTP client, even curl in a loop.
"The frontend team wanted something quick." SSE requires server-side connection management. WebSockets need a load balancer that supports sticky sessions or connection-aware routing. Polling is a GET request in a setInterval. It ships in 30 minutes.
"We'll optimize later." Except "later" is always after the next feature. By the time polling becomes a problem, it's baked into the client SDK, documented in the API docs, and used by external integrators. Ripping it out is a breaking change.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.