Vivek Jalondhara. All writing

Architecture

Polling vs WebSockets for Price-Import Progress

By Vivek Jalondhara · 5 min read
Published

Choose a progress transport around how quickly users need updates, while keeping a recoverable import status behind either option.

A vendor sheet has uploaded successfully, but parsing and review preparation are still running. The user needs to know whether to wait, inspect flagged rows, or recover from a failure. Upload completion alone cannot answer those questions.

For this educational Sheet Parser scenario, compare polling and WebSockets against the same operation status contract. The examples describe a possible design exercise, not the project’s private architecture. Keeping the status contract constant makes the transport tradeoff easier to examine.

Define what progress means first

Start with named stages such as queued, parsing, awaiting-review, and failed. Avoid presenting a percentage unless the denominator has a defensible meaning. Ten parsed rows out of an unknown number of source rows is useful activity information, but it is not a reliable promise that a job is halfway done.

Give each operation a durable identifier and each committed status snapshot an increasing revision. A refresh or reconnect should retrieve the current authorised snapshot. A live connection can improve delivery speed; it should not be the only place where the operation’s outcome exists.

Use polling when delay is acceptable

A polling client asks for the latest status after the previous request finishes. Scheduling the next request after completion avoids overlapping requests when a response is slow. Stop when the operation is terminal, and use a bounded backoff after transient failures rather than repeatedly issuing requests at a fixed rapid interval.

MDN’s conditional-request documentation describes validators such as ETags. A status endpoint can use them to avoid resending an unchanged representation. Handle a 304 response without trying to parse an absent JSON body. Authentication and authorisation still apply to that request, including when the stored representation has not changed.

Use WebSockets when updates justify a connection

MDN describes WebSockets as a way to exchange messages over a persistent connection. That can suit a screen monitoring several changing imports where prompt updates meaningfully affect the user’s next action. It also introduces connection establishment, subscription management, reconnect behaviour, and an additional operational path to observe.

The ordinary browser WebSocket API lacks built-in backpressure. For this scenario, coalesce replaceable progress snapshots and bound outgoing queues instead of preserving every intermediate counter value indefinitely. Keep durable results elsewhere. Intermediate progress is expendable; losing the final review outcome should be recoverable by reading status again.

Recover without trusting arrival order

A client might request a snapshot, receive a newer push message, and then receive the older HTTP response. Comparing operation identifiers and revisions prevents that delayed response from moving the screen backward. Compare against the actively selected operation even when no snapshot has arrived yet, so an old request cannot repopulate a newly reset view. A revision is meaningful only if the server assigns it consistently with the status mutation.

On reconnect, fetch the current snapshot and reconcile it with subsequently received messages. For complete snapshots, accepting the greatest validated revision can tolerate an earlier message being missed. Delta-only messages need a separate replay or resynchronization protocol, because the highest sequence number alone cannot reconstruct omitted changes.

Apply the same access rules to both paths

A job identifier is an address, not permission to observe the job. Protect polling reads and WebSocket subscriptions with the same tenant and operation ownership rules. Define how a membership change ends future access, including subscriptions that were authorised before the change.

Closing a tab or aborting a polling request should stop observation. It should not silently cancel the server-side import unless the product explicitly defines that action. Offer cancellation as its own command with a documented outcome so users can distinguish leaving the page from requesting that work stop.

Choose using an observable user need

For a single import that commonly waits for human review, begin by asking how much status delay users can tolerate. If that tolerance comfortably allows periodic reads, polling has a straightforward failure path. If users coordinate several active operations and repeatedly need immediate changes, a push channel may justify its extra responsibilities.

Compare these choices with synthetic slow responses, dropped connections, and navigation between operations. Record whether the user can recover the correct status and whether the client stops unnecessary work. Neither transport is inherently the winner; the useful result is a justified choice with a recovery path that behaves the same after refresh.

Ignore an older complete status snapshot

Educational reducer fragment for already schema-validated complete snapshots. Pass the actively selected operation identifier separately from current state. Revisions are nonnegative safe integers assigned by the authoritative service.

Illustrative example · JavaScript · Simplified for explanation

export function latestSnapshot(current, incoming, expectedOperationId) {
  const retained = current?.operationId === expectedOperationId ? current : null;
  if (incoming.operationId !== expectedOperationId) return retained;
  if (!Number.isSafeInteger(incoming.revision) || incoming.revision < 0) {
    throw new TypeError("Invalid status revision");
  }
  return retained === null || incoming.revision > retained.revision
    ? incoming
    : retained;
}
How the example fits together
  1. Read the authorised operation snapshot
  2. Observe with polling or a subscription
  3. Validate identity and revision
  4. Recover current status after reconnect

Conceptual flow for the example above.

Tradeoff to keep in mind

This is a client ordering guard, not a full transport or schema validator. Pass the current selection when handling each response, and enforce authorisation and legal state transitions on the server.

Related project: Sheet Parser →

References & further reading

Related posts