🚗 Analogy
Polling is the kid in the back seat: “Are we there yet? … Are we there yet?” The client just keeps asking. Short polling asks on a timer; long polling asks once and waits quietly until the driver actually has an answer, then immediately asks again. It’s the least clever approach — which is exactly why it’s so reliable.
Short polling: ask on a timer
The client requests the same endpoint every few seconds; the server answers with the current state. Dead simple, works through any proxy — here’s the loop, driven in-process:
graph LR C["client"] -->|"GET /status (t=0s)"| S["server"] S -->|"version=1"| C C -->|"GET /status (t=5s)"| S S -->|"version=2"| C C -->|"GET /status (t=10s)"| S S -->|"version=3"| C
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
func main() {
// server state that changes between requests
version := 0
status := func(w http.ResponseWriter, r *http.Request) {
version++ // pretend new data arrived since the last poll
fmt.Fprintf(w, "version=%d", version)
}
// the client polls the same endpoint on a timer (here: 3 times)
for poll := 1; poll <= 3; poll++ {
rec := httptest.NewRecorder()
status(rec, httptest.NewRequest("GET", "/status", nil))
fmt.Printf("poll %d -> %s\n", poll, rec.Body.String())
}
}
In real code the loop is a time.Ticker; a polite client adds jitter and backoff so a thousand clients don’t all hit the server on the same second (the thundering herd).
Long polling: hold the request open
Instead of answering immediately, the server parks the request until there’s something new (or a timeout fires), then responds; the client re-requests at once. Fewer empty round-trips, near-real-time — at the cost of a held connection per waiting client:
func longPoll(w http.ResponseWriter, r *http.Request) {
select {
case ev := <-updates: // data arrived — answer now
fmt.Fprint(w, ev)
case <-time.After(30 * time.Second): // nothing yet — let the client retry
w.WriteHeader(http.StatusNoContent)
case <-r.Context().Done(): // client gave up
return
}
}
Choosing
graph TD Q["need updates?"] --> A["rarely / non-urgent<br/>→ short polling"] Q --> B["near real-time, server → client<br/>→ long polling or SSE"] Q --> C["two-way, high frequency<br/>→ WebSockets"]
Reach up the ladder only when you need to: short polling → long polling → SSE → WebSockets. Each step adds real-timeness and complexity.
🐹 Boring is a feature
Polling has no special server, no held connections (short polling), and no proxy surprises — it just works, everywhere. Add backoff with jitter to avoid synchronized stampedes, send a small ETag/If-None-Match or a since cursor so unchanged polls return a cheap 304 Not Modified, and use a context timeout on the client. Don’t reach for WebSockets to update a number every minute.
See also
- Server-Sent Events — server push without holding a request open per poll.
- WebSockets — full-duplex when you’ve outgrown polling.
- HTTP client — the client side, with timeouts and backoff.
- rate limiting — jitter/backoff to avoid the thundering herd.
Next: the inverse arrangement — let the other service knock on your door with Webhooks.
Related topics
One-way push over plain HTTP — the text/event-stream format, flushing events, auto-reconnect with Last-Event-ID, and when SSE beats WebSockets.
apisWebSocketsFull-duplex, persistent connections over one TCP socket — the HTTP Upgrade handshake, frames, ping/pong keepalives, and when to choose them over SSE or polling.
httpHTTP ClientCalling services with net/http — http.Get vs a configured http.Client with timeouts, building requests and posting JSON, checking status, closing and draining the body, connection reuse via Transport, and context cancellation.
Check your understanding
Score: 0 / 51. What is the difference between short polling and long polling?
Short polling asks every N seconds whether you like it or not — simple but wasteful. Long polling parks the request server-side until there's something to send (or a timeout), giving near-real-time updates with far fewer empty responses.
2. What's the main downside of frequent short polling?
If data changes rarely, most polls return 'no change', so you pay request overhead for nothing — and you still lag up to one interval behind. Long polling, SSE or WebSockets fix that; short polling wins on sheer simplicity.
3. When is plain polling actually the right choice?
Polling needs no special server infrastructure and sails through any HTTP proxy or firewall. For a status that updates every few minutes, a 30-second poll is simpler and more robust than holding open connections.
4. What does long polling trade away to get near-real-time updates?
Long polling parks each request server-side until data is ready, so every waiting client ties up a connection and a goroutine. That's cheap in Go (goroutines are light) but still bounded — past SSE/WebSocket scale you must cap concurrent waiters and set a timeout so parked requests eventually return.
5. How can a polling client make 'nothing changed' responses cheap?
Conditional requests turn an unchanged poll into a tiny 304 Not Modified (no body), saving bandwidth and serialization. The client sends the last ETag in If-None-Match (or a ?since= cursor); the server compares and either returns 304 or the new data with a fresh ETag.
Comments
Sign in with GitHub to join the discussion.