Real-Time APIs · Real-Time Transports

Long Polling and Fallbacks

When persistent connections aren't available.

Long polling: the client sends a request that the server holds open until data is available (or a timeout, e.g., 25 s), then the client immediately reconnects. It works everywhere HTTP works and is a solid fallback for restrictive networks. Use cursors so each poll resumes from the last event, and keep server timeouts below proxy/LB idle timeouts. Libraries like Socket.IO and SignalR negotiate WebSockets and fall back automatically.

Polling styles

Criterion Short polling Long polling
Latency Up to interval Near real-time
Empty responses Many Few
Server resources Per request Held connections
Compatibility Universal Universal
@app.get("/updates")
async def updates(cursor: int = 0, user: CurrentUser = None):
    try:
        async with asyncio.timeout(25):                   # below LB idle timeout
            items = await feed.wait_for_items(user.id, after=cursor)
    except TimeoutError:
        items = []
    next_cursor = items[-1]["seq"] if items else cursor
    return {"items": items, "cursor": next_cursor}         # client immediately polls again

Common pitfalls

  • Hold times longer than load balancer idle timeouts.
  • No cursor (missed or duplicate updates).
  • Thundering reconnects after deploys (add jitter).

Key points

  • Hold, respond, reconnect
  • Cursors for continuity
  • Jittered reconnects

Further reading: RFC 6202: Known issues and best practices for long polling

Practice questions

Why should the long-poll hold time be shorter than the load balancer's idle timeout?

Answer

Otherwise the LB closes the connection first, causing errors and reconnect churn instead of a clean empty response.

How do cursors prevent missed updates in long polling?

Answer

Each response returns the last delivered sequence; the next request asks for items after it, so updates arriving between polls are still delivered.

Open in the app to mark lessons complete, take notes and track your progress.