Skip to content

Design prompt: a chat / messaging UI

A chat client trusts two numbers: the id it makes at the tap, and the sequence the server gives each conversation.

Predict first

Before you read: predict

A buyer and a seller chat on an order's checkout page, and the chat service relays each message to both. Each device shows its own message the moment Send is tapped and adds the other side's messages as they arrive. The seller's laptop clock runs 2 minutes fast. At 12:05:00 the seller asks "Size M or L?", and at 12:05:20 the buyer answers "L, please". At 12:06:00 both tap Send before either sees the other's message: the buyer "Is shipping free?", the seller "Coupon applied". The team wants both screens to list the messages in the same order, with no answer above its question.

Which sort key should both clients use?

Pick one

The 30-second version

Give each message a client id at the tap and show it at once as sending; your own copy returning with that id confirms the bubble. The server numbers each conversation's messages: order and resume by that number, never device clocks, and show each number once. A gap is a missing message. Receipts say "read up to N".

A chat client trusts two numbers: the id it makes at the tap, and the sequence the server gives each conversation.

How it works

Step 1 of 13

One socket, every chat. One WebSocket carries all the user's conversations, and each frame names its conversation; choosing the channel and reconnecting are fsd-realtime-transport's.

Analogy: A chequebook and the bank statement

Each cheque carries its number before you write it, and you fill in its stub at once. The bank pays cheques in its own order and prints each one as a numbered line on that account's statement.

With the chequebook In the chat client
the number printed on each cheque before you write it the client id made at the tap (step 2)
the stub, filled in at once and marked "not cleared" the pending bubble (step 3)
ticking a stub against the statement line that quotes its cheque number, never its amount confirming by client id (step 5)
the statement's line order, whatever date each account holder wrote on a cheque the server's sequence, not device clocks (steps 4, 6)
a separate statement, with its own line numbers, for each account a sequence per conversation (step 8)
line 17 followed by line 19 a message you lack (step 9)
"checked to line 18" pencilled on the statement, never rubbed back a read mark that only rises (step 12)

In real terms: every message carries two numbers: the client's id, made before sending, and the server's sequence, set on acceptance.

Breaks down when: the bank never learns which lines you checked, while a chat sends its read mark to the other members (steps 12–13). A bank also refuses a cheque presented twice on its own; a chat server keeps one message per client id only if it is built to (api-idempotency-keys).

Contrasting case: a diary kept by one writer needs no numbering: one hand, one order.

Examples, run for real

4.1 ExamHall EXH-2250 → MR !2550: a read mark that went backwards

In a dispute review, candidate c-41's message "My screen froze at Q7" showed as unread by the proctor, though the proctor had answered it. The proctor read the chat on the hall tablet; the desk console's older receipt reached the chat service later, and the service stored the latest receipt. MR !2550 keeps the highest.

EXH-2250 receipts in arrival order: tablet up to 12, desk console up to 9
EXH-2250 latest receipt kept  · proctor's mark 9 · candidate's message 12 shows unread
MR !2550 highest receipt kept · proctor's mark 12 · candidate's message 12 shows read

A reader's devices report on their own, so the last receipt to arrive need not be the furthest one read (step 12).

4.2 Proof domain, e-commerce checkout: loading older messages

A buyer scrolls up an order-help chat that shows messages 31 to 45. The client asks for the ten messages before the oldest one it holds.

checkout history · on screen 31-45 · asks for 10 before 31 · received 21-30 · on screen 21-45, 25 messages

The request names the oldest sequence held (step 11).

4.3 The board for "Architect the frontend for a messaging system" (worked, not executed)

Typical question Steps that answer it Mechanism owner
"A message is sent while offline. Walk me from tap to delivered." 2–5, 10, 12 this lesson; the outbox's storage and retries: fsd-client-retry-resilience
"How do you avoid duplicate messages after a reconnect?" 7–8 this lesson; reopening the socket: fsd-realtime-transport
"Architect the frontend for a messaging system." all thirteen, opened as two deep dives the round's phases: fsd-radio-framework

Picture it

A sequence diagram across sender, message service, durable store and recipient: a send with a client message ID, a missed live event, and a reconnect after cursor 120.
An illustration to help it stick. The mechanism is in the sections above.

Try it

Answer first, then check. Nothing is saved.

Question 1 of 5

The buyer's chat client draws a "sending" bubble the moment Send is tapped, then sends { type: 'send', cid, text }. For each accepted message the server sends every socket in the chat, the sender's included, { type: 'msg', seq, cid, from, text }, copying the send frame's cid; then it sends the sending socket { type: 'ack', seq }. Today the client sends cid: null and adds every msg as a new bubble, so each message the buyer sends shows twice. The seller asks "Gift wrap?" and "Same address?", and the buyer replies "Yes" to each.

Which change leaves one bubble per message, each ending as sent?

Pick one

Question 2 of 5

The proctor console holds messages 1–6 of candidate c-41's chat when the hall Wi-Fi drops. When its new socket opens, it subscribes and at once calls history({ after: 6 }); both paths pass each message to onMessage(m). During the outage the candidate sent 7 and 8, and she sends 9 after the new subscription starts but before the history read.

Which onMessage shows 1–9 once each, in order, whichever path delivers first?

Pick one

Question 3 of 5

The buyer's client sends each message with post(m), a POST /messages whose promise settles when the server answers, and receives every message of her chats, her own included, on the socket; the server numbers each chat's messages in the order their POSTs reach it, and every client lists a chat by that number. On a train with no signal she writes, in this order, "Can I swap to size L?" and "And add gift wrap" to the seller, "Please leave it with the porter" to the courier, and "Thanks!" to the seller. They wait in outbox in that order; byChat(outbox) returns one array per chat, each in written order.

Which flush, run when the signal returns, guarantees that the seller chat lists her messages in the order she wrote them on both screens, without the courier message waiting for any seller message's reply?

Pick one

Question 4 of 5

During ECON-201, candidate c-17's chat shows messages 1–9 from the proctor. While the chat panel is open, the client sends { type: 'read', upTo } each time it shows a message; the server keeps each reader's highest upTo, and the proctor console marks Read every message whose number is at or below it. After a Wi-Fi drop, the new socket delivers message 11 while the catch-up request for 10 is still pending.

Which rule for upTo keeps the console from marking Read a message the candidate has not been shown, and marks 11 Read once 10 is shown?

Pick one

Question 5 of 5

The buyer has two chats open: the seller's, whose newest message on her screen is number 41, and the courier's, whose newest is number 7. Her client keeps one cursor:

While her socket was down, the seller sent "Refund approved" and "It reaches your card in 3 days", and the courier sent "At your door" and "Left with your neighbour".

What do her chats show after onReconnect() finishes?

Code for this question
let lastSeq = 0;socket.onmessage = (e) => { const m = JSON.parse(e.data); lastSeq = Math.max(lastSeq, m.seq); show(m); };async function onReconnect() {  for (const chat of openChats) for (const m of await history(chat, { after: lastSeq })) show(m);}
Pick one

Go deeper

Pro tip
  1. When asked to walk a message from tap to delivered, draw its life as one sequence diagram before any component tree: the tap, the pending bubble, the server's number, each device's delivery, then read. When: a frontend round on a messaging product (choosing the deep dives is fsd-radio-framework's). Status: SELF (adapted from the curriculum row's tip); not run.
Source

Checked against the tools and docs as of 2026-10-01.