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?
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
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
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?
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?
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?
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?
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?
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);}Go deeper
Pro tip
- 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
- Front End Interview Handbook, Front End System Design (the curriculum row's URL) and its Applications sub-page: https://www.frontendinterviewhandbook.com/front-end-system-design and https://www.frontendinterviewhandbook.com/front-end-system-design/applications · fetched 2026-10-01, saved as
sources/fih-overview.txtandsources/fih-applications.txt. Both list "Chat application (e.g. Messenger)" as a paid example question; its page was not read. Valid as of 2026-10-01. - Meta, Software Engineer Full Loop Interview Guide (the curriculum row's URL): https://d3no4ktch0fdq4.cloudfront.net/public/course/files/Meta_SWE_full_loop_guide.pdf · fetched 2026-10-01, reduced to text with pdftotext, saved as
sources/meta-full-loop.txt(bytes assources/meta-full-loop.pdf). "How would you architect the front-end for a messaging system?" and "What should the experience be while you're waiting for confirmation?". A company practice: valid as of 2026-10-01. - RFC 6455, The WebSocket Protocol (the curriculum row's URL): https://www.rfc-editor.org/rfc/rfc6455 · fetched as https://www.rfc-editor.org/rfc/rfc6455.txt on 2026-10-01, saved as
sources/rfc6455.txt. "basic message framing, layered over TCP"; a mechanism for "two-way communication with servers that does not rely on opening multiple HTTP connections" (step 1). Valid as of 2026-10-01. - MDN, Crypto: randomUUID() method: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID · fetched 2026-10-01, saved as
sources/mdn-randomuuid.txt. It generates "a v4 UUID using a cryptographically secure random number generator" and is "available only in secure contexts (HTTPS)" (step 2;http://127.0.0.1is a secure context, so the runs use it). Valid as of 2026-10-01. - MDN, The WebSocket API (WebSockets): https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API · fetched 2026-10-01, saved as
sources/mdn-websockets-api.txt. "a two-way interactive communication session between the user's browser and a server" (step 1). Valid as of 2026-10-01.
Related lessons
Checked against the tools and docs as of 2026-10-01.