renderpx
Theme: auto

Polling vs WebSockets

When to refetch on a timer (polling) and when to push updates from the server (WebSocket). Use the right tool for latency and scale.

The problem I keep seeing

Data on the server changes and the client needs to show it. Two main options: the client asks periodically (polling) or the server pushes when something changes (WebSocket, SSE). Polling is simple and works everywhere, but it wastes requests when nothing changed and adds latency (up to one full interval). WebSockets give instant updates and less traffic when updates are frequent, but they need connection management, reconnection, and backend support.

The mistake is picking one for the whole app. Use polling for slow-changing data (notifications every 30s, dashboard every minute). Use WebSockets for chat, presence, or live collaboration where sub-second latency matters. Often you want both: WebSocket for real-time and a poll interval as fallback when the connection drops.

Naive approach

Fetch once on mount. Data goes stale; the user has to refresh to see new content. For “live” features that’s not acceptable.

(Omitting code—same as a basic useQuery with no refetchInterval or WebSocket.)

First improvement

Use React Query’s refetchInterval. The query refetches every N ms. Set refetchIntervalInBackground: false so you don’t burn requests when the tab is hidden. Simple and good enough for many UIs (notifications, dashboards).

tsx
Loading...

Why this helps: Data stays reasonably fresh with no extra infrastructure. Tune the interval to your UX and server capacity (e.g. 30s for notifications, 5s for a live scoreboard).

Remaining issues

  • Latency: With a 30s poll, updates can be up to 30s late. For chat or live collaboration that’s too slow—you need push (WebSocket or SSE).
  • Waste: Polling when nothing changed wastes bandwidth and server work. For high-frequency updates, push is more efficient.
  • Integration: When you add a WebSocket, keep using React Query for the data. The WebSocket’s job is to trigger invalidateQueries (or setQueryData) so the existing cache and UI update; you don’t replace useQuery with raw WS state.

Production pattern

For real-time: open a WebSocket in a hook (or context); on message, invalidate the relevant query keys (or set data). The component still uses useQuery for loading, error, and caching. Keep a refetchInterval as a fallback so if the WebSocket disconnects, polling still updates the data until reconnection.

tsx
Loading...
Hybrid: WebSocket + polling fallbacktsx
Loading...

When to use which

  • Polling: Notifications, dashboard metrics, “last updated” — low frequency, simple.
  • WebSocket: Chat, presence, live collaboration — high frequency or sub-second latency.
  • Hybrid: Use WS for real-time, keep refetchInterval as fallback when WS disconnects.

When I use this

  • Polling: Notifications, dashboard tiles, “last updated” timestamps—anything that can tolerate 15–60s delay. Simpler and easier to scale (stateless HTTP).
  • WebSocket: Chat, presence, live cursors, real-time collaboration. When the user expects instant feedback or the update rate is high.
  • Hybrid: Use WebSocket for the real-time channel and keep refetchInterval as backup. Handle reconnect (e.g. exponential backoff) and optionally refetch on focus.

SSE

Server-Sent Events are a middle ground: server pushes, one-way. Good for live feeds where the client doesn’t need to send messages. Simpler than WebSockets on the server; use EventSource and invalidate on event.

Gotchas

  • Reconnect: WebSockets drop (network, load balancer). Implement reconnect with backoff and optionally re-subscribe (e.g. send a “sync” message) so you don’t miss updates during the gap.
  • Stale closure: In the WS onmessage, use queryClient from the hook so invalidation runs with the current client. Avoid closing over an old reference.
  • Tab visibility: With polling, refetchIntervalInBackground: false avoids work when the tab is hidden. For WebSockets, you may still want to reconnect or refetch when the user comes back (refetchOnWindowFocus).

Data Fetching & Sync → · Cache Invalidation → · All patterns