Short polling for presence

Clients keep asking the server “anything new?” on a fixed clock, instead of being told.

The idea

A presence system shows which users are online — the little green dot next to a name. With short polling, each client sends an HTTP request every few seconds asking “is anything different?” The server answers immediately with the current state and the connection closes. Nobody holds anything open.

It is simple and firewall-friendly: it is just normal request/response, so it works anywhere a web page loads. But it scales poorly. Most answers are empty (“no change”), the freshest a peer can ever be is one poll interval behind reality, and total load grows with clients × poll frequency. Push models (long-poll, SSE, WebSocket) flip this: the server speaks the moment something changes.

See it work

Press play to watch it run.

How it works

The client side is a plain loop on a timer: ask, render, wait, repeat. The server has no memory of who is listening — it just answers whatever is true at the instant the request lands.

// Each client runs this independently.
const INTERVAL = 5000;            // ms — the poll period
let lastSeen = null;              // version/etag of the last state we drew

function pollPresence() {
  fetch('/presence?since=' + lastSeen)
    .then(r => r.json())
    .then(state => {
      if (state.changed) {        // server says something moved
        lastSeen = state.version;
        renderRoster(state.online); // redraw the green dots
      }
      // else: empty response, nothing to do — a wasted round trip
    })
    .catch(() => {/* coach note: back off & retry, don't hammer */});
}

// Add jitter so 10k clients don't all fire on the same tick.
const jitter = Math.random() * 1000;
setInterval(pollPresence, INTERVAL + jitter);

Trade-offs

AspectCostSignal to watch
LatencyUp to one poll interval before a change is seenTime from event to roster update
Server loadScales with clients × poll frequencyRequests per second at peak concurrency
Wasted bandwidthMost responses are empty “no change”Share of polls returning nothing new
ComplexityVery low — plain request/response, firewall-friendlyHow much infra you would add to avoid it
When to pick itSmall scale, simple infra, loose freshness needsConcurrent clients and acceptable staleness

Watch out for

Worked example

Say you have 500 users in a room and a 5 second poll interval. Each client fires 1 request every 5s, so the room generates 500 / 5 = 100 presence requests per second — ~360,000 per hour — whether or not anyone’s status actually changed.

Now suppose presence changes only a handful of times a minute. The overwhelming majority of those 100 requests/second come back empty: the “wasted polls” tally in the animation above is the whole room in miniature. And when a user does go offline, peers keep showing them online until their next poll — a detection lag of up to the full 5 second interval. A push model would close that gap to roughly 0 ms and send almost no empty traffic, at the cost of holding open connections and more moving parts.

Check yourself

A user goes offline 1 second after their last poll cycle, on a 5 second interval. What’s the most time before peers can see them as offline?

The room feels laggy, so you halve the poll interval. What happens to steady-state server load?