Skip to content
All notes

Server Authority Without a Sluggish Interface

Predict locally, decide centrally, and make rejection a first-class state.

Systems design3 min read
Contents

Real-time products have a tension:

  • waiting for the server after every interaction feels slow;
  • trusting the client makes shared state unreliable.

The useful answer is not to choose one. It is to give the client permission to predict and the server authority to decide.

Prediction is not truth

In Real-Time Chess, the browser keeps a local chess.js instance. When a player drags a piece, the client checks the move and updates its own board immediately.

That local result exists for one reason: reduce perceived latency.

The server independently checks:

  1. the room exists;
  2. the socket belongs to a player;
  3. the player's color owns the current turn;
  4. the move is legal against the server's board;
  5. the new room snapshot can be saved.

Only after those checks does the server broadcast the move.

The client predicts. The server commits.

Design rollback before optimism

Optimistic UI is incomplete if rejection has no recovery path.

The chess client keeps the synchronized FEN position from the room snapshot. When the server emits invalid-move, the browser restores that FEN. If no snapshot is available, it undoes the local move.

That pattern generalizes:

ProductOptimistic actionRollback source
ChessMove a pieceLast server board version
KanbanMove a cardPrior column and position
ChatShow pending messageLocal pending record
EditorApply local operationOperation log or fresh document snapshot

The rollback source should be defined before the optimistic animation ships.

Correlate the prediction and decision

A basic protocol can identify the sending socket and let it ignore its own broadcast. That works for one in-flight action, but becomes ambiguous with retries or rapid commands.

A stronger request carries a client-generated ID:

socket.emit("move", {
  clientMoveId: "01J...",
  roomId: "abc123",
  move: { from: "e2", to: "e4" },
});

The accepted event echoes it:

{
  clientMoveId: "01J...",
  version: 18,
  move: { from: "e2", to: "e4", san: "e4" }
}

Now the client can:

  • mark the exact optimistic action as confirmed;
  • measure round-trip latency;
  • retry without duplicating the command;
  • reject or reconcile one action without guessing.

Version shared state

Sender IDs answer "did I originate this?" Versions answer "am I still synchronized?"

Each accepted command should advance a monotonic room or document version:

snapshot v17 → accepted move → event v18

If a client receives v19 while holding v17, it can stop applying events and request a fresh snapshot. Silent gaps become observable recovery paths.

Without versions, the UI can look correct while holding a different history from the server.

Rejection copy matters

A server-authoritative system rejects actions during normal operation:

  • stale state;
  • lost permissions;
  • wrong turn;
  • expired seat;
  • concurrent update;
  • invalid domain command.

Do not collapse all of them into "Something went wrong."

The user needs enough information to recover:

Not your turn. Wait for your opponent.
This board changed. We restored the latest position.
Your room seat expired. Rejoin the game.

The protocol can use machine-readable codes while the UI provides specific language.

Server authority is not one server

Authority means one logical decision point, not necessarily one process.

When the system scales across instances, the same rules need shared coordination:

  • consistent room storage;
  • a Socket.IO adapter for cross-instance fan-out;
  • transactional or versioned writes;
  • idempotency keys;
  • a per-room queue when commands must be serialized.

If two servers can accept different transitions from the same state version, the architecture is no longer authoritative.

When not to use optimistic UI

Optimism is a product choice, not a default.

Wait for confirmation when:

  • the action is destructive and hard to reverse;
  • the rejection rate is high;
  • local prediction cannot represent the server rule;
  • another user would observe the provisional state;
  • the cost of a visible rollback exceeds the latency benefit.

For those actions, communicate pending state and let the server decide first.

The working rule

For fast shared interfaces:

predict locally
identify the command
validate centrally
commit once
broadcast the decision
confirm or roll back
recover from version gaps

Server authority does not require a sluggish UI. It requires a clear line between what the browser may predict and what the system is willing to call true.

Notes from the build

Get more AI engineering insights

Follow the work: AI tools, browser products, product decisions, and honest lessons from the build.

By subscribing, you agree to receive Orlando's emails. No spam. Unsubscribe anytime.

Server Authority Without a Sluggish Interface | Orlando Ascanio