Message Ordering
Queues do not guarantee message ordering by default — especially when multiple workers are consuming in parallel. Ordering must be designed explicitly, using a combination of client-side sequence numbers and server-side timestamps.
The problem#
You have a chat app. User Alice sends three messages in a conversation:
These need to arrive at Bob's screen in exactly that order. If Bob sees "you there?" before "hey" — the experience is broken.
Now your queue has multiple workers consuming messages in parallel:
Worker A gets "you there?" → processes fast → done
Worker B gets "how are you" → processes slow → done
Worker C gets "hey" → processes slow → done
Result at Bob's screen: "you there?", "how are you", "hey" ← completely wrong
Multiple workers processing in parallel destroys ordering. The queue hands out messages as fast as it can — it doesn't care which worker finishes first.
Why client timestamps alone don't work#
First instinct: stamp each message with the client's clock, sort by timestamp at the consumer.
Two things break this.
Problem 1 — Network jitter reorders messages in transit
Alice sends both messages in the right order, at the right time. But the network is not a perfect pipe.
Alice sends:
"hey" at client_ts: 10:00:00.100 → takes 200ms to reach server
"how are you" at client_ts: 10:00:00.150 → takes 20ms to reach server
Server receives:
"how are you" arrives at 10:00:00.170 ← arrives first
"hey" arrives at 10:00:00.300 ← arrives second, despite being sent first
If the server trusts client timestamps, it sorts them correctly: "hey" then "how are you". But the server received them in the wrong order and processed "how are you" first. Without a reorder buffer, the damage is already done.
Problem 2 — Client clocks drift
Every phone has its own clock. Those clocks are not perfectly in sync — they can be off by hundreds of milliseconds or even seconds.
Alice's phone clock: 10:00:00.100 (slightly fast)
Bob's phone clock: 10:00:00.050 (slightly slow)
Alice sends "hey" with client_ts: 10:00:00.100
Bob sends "yeah I'm here" with client_ts: 10:00:00.050
Sorted by client_ts: Bob's message appears first — even though Alice sent first
You're now ordering a conversation based on whose phone clock happens to be more accurate. That's not a system — that's luck.
Why server timestamps alone don't work either#
Second instinct: let the server stamp messages as they arrive.
The problem: the server receives messages in whatever order the network delivers them. If "how are you" arrives before "hey" at the server, the server assigns it a lower sequence number — wrong order preserved forever.
The right solution — client sequence numbers + server timestamps#
You need both working together.
Step 1 — Client assigns sequence numbers per sender per conversation
Each sender maintains a counter for each conversation they're participating in. Every message they send gets the next number in that counter.
Alice sending in conv_id: 123:
"hey" → sender: Alice, conv_id: 123, client_seq: 1
"how are you" → sender: Alice, conv_id: 123, client_seq: 2
"you there?" → sender: Alice, conv_id: 123, client_seq: 3
The (conv_id, sender, client_seq) triple uniquely identifies a message's intended position. Even if the network delivers them out of order, the server can reorder them correctly.
Step 2 — Server reorders by client_seq before storing
Server receives in order: client_seq 2, then 3, then 1
Server sorts by client_seq → 1, 2, 3
Server stores in correct order, assigns server-side IDs
Step 3 — Interleaving messages from multiple senders
The sequence number is per sender — Alice has her own counter, Bob has his own. To display the full conversation correctly, the server uses its own timestamp to interleave both senders' messages.
Alice sends in conv_id: 123:
"hey" → sender: Alice, client_seq: 1, server_ts: 10:00:00.050
"how are you" → sender: Alice, client_seq: 2, server_ts: 10:00:00.150
"you there?" → sender: Alice, client_seq: 3, server_ts: 10:00:00.400
Bob replies in conv_id: 123:
"yeah I'm here" → sender: Bob, client_seq: 1, server_ts: 10:00:00.250
"what's up?" → sender: Bob, client_seq: 2, server_ts: 10:00:00.350
Final conversation sorted by server_ts:
1. Alice: "hey" (10:00:00.050)
2. Alice: "how are you" (10:00:00.150)
3. Bob: "yeah I'm here" (10:00:00.250)
4. Bob: "what's up?" (10:00:00.350)
5. Alice: "you there?" (10:00:00.400)
The client_seq guarantees each sender's messages are in the right relative order. The server timestamp interleaves the two senders correctly.
Can a queue guarantee ordering natively?#
Most simple queues — no. When you have multiple workers consuming in parallel, ordering is destroyed. The solution is to design ordering at the application level as described above.
Some advanced systems like Kafka handle this differently. Kafka guarantees ordering within a partition — all messages with the same partition key (e.g. conv_id) go to the same partition and are consumed in order by one consumer. This means one worker handles all messages for a given conversation, preserving order natively. But that's a Kafka-specific mechanism covered in the Kafka deep dive.
Summary of the ordering strategy#
| Layer | Responsibility |
|---|---|
| Client | Assigns client_seq per sender per conversation |
| Network | Delivers in any order — unreliable |
| Server | Reorders by client_seq before storing; uses server_ts to interleave senders |
| Consumer | Reads messages already in correct order from storage |
Interview framing: "For message ordering in a chat system, I'd have each client assign a sequence number per conversation. The server reorders on arrival using the client sequence number before storing. For interleaving messages from different senders in the same conversation, I'd use the server-assigned timestamp as the canonical order — client clocks are unreliable."
Never rely on client-side timestamps for ordering across different senders. Two devices' clocks can drift. Always use server-side timestamps as the canonical interleaving mechanism.