MQTT guarantees message order only within a narrow scope: messages from one publisher, on one topic, at one QoS level are delivered to each subscriber in the order the broker received them. Across topics, across publishers and inside shared subscriptions there is no ordering guarantee — and even within the guaranteed scope, QoS 1 retransmissions can produce late duplicates.
This article explains exactly what the specification promises, where ordering breaks in practice, and gives you two short Python scripts that measure out-of-order and duplicate messages against your own broker.
What the specification says
Both MQTT 3.1.1 and MQTT 5 (section 4.6, "Message ordering") define every topic as an Ordered Topic by default. When a broker forwards messages on an ordered topic, it must send PUBLISH packets to subscribers "for the same Topic and QoS" in the order they were received from any given client. Clients, in turn, must resend unacknowledged messages and send acknowledgements in order.
The specification also points out the catch. With QoS 1, a publisher sending 1, 2, 3, 4 can lead a subscriber to see 1, 2, 3, 2, 3, 4: after a reconnect, the retransmitted copies of 2 and 3 arrive after the originals. The final copy of each message is in order, but a duplicate can show up after its successor. If no more than one message is in flight at a time, this cannot happen.
| Scenario | Order guaranteed? |
|---|---|
| One publisher, one topic, same QoS | Yes, per subscriber |
| One publisher, several topics | No — interleaving between topics is undefined |
| Several publishers, one topic | Only per publisher; interleaving between them is arbitrary |
| Same publisher, mixed QoS on one topic | No — the rule applies per QoS |
| QoS 1 after a reconnect | Final copies in order; duplicates can arrive late |
| QoS 0 under load | Order kept for what arrives, but messages can be missing |
| Shared subscription group | No — messages are spread across members |
MQTT QoS levels covers the acknowledgement flows behind these rules.
Where ordering breaks in practice
- Multiple in-flight messages and reconnects. Clients and brokers usually allow many unacknowledged QoS 1/2 messages at once (in MQTT 5 the limit is negotiated with Receive Maximum). If the connection drops with several in flight, the resend can land after newer messages.
- Shared subscriptions.
$share/group/topicdistributes messages across consumers. Each consumer may see its share in order, but processing runs in parallel, so the overall result is unordered. See shared subscriptions. - Several publishers. Two gateways reporting the same device produce two independently ordered streams. The broker may interleave them in any way.
- Bridges and clusters. A bridge is just another client, so one bridge connection keeps order; multiple paths between brokers do not. Clustered brokers generally preserve per-publisher order on a topic, but check your broker's documentation.
- Your own consumer. The most common cause of all — messages arrive in order and are then processed concurrently (thread pools, async handlers, worker queues).
Test 1: measure ordering on your broker
The publisher numbers messages per topic and per publisher ID. The subscriber tracks the highest sequence it has seen per (topic, publisher) and reports anything that goes backwards, any duplicate and anything missing.
import os, sys, time
from collections import Counter, defaultdict
import paho.mqtt.client as mqtt
HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
RUN = sys.argv[1] # same run id as the publisher
QOS = int(os.getenv("MQTT_QOS", "1"))
last = defaultdict(lambda: -1) # (topic, publisher) -> highest seq
seen = defaultdict(set)
stats = Counter()
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(f"testmqtt/order/{RUN}/#", qos=QOS)
def on_message(client, userdata, msg):
pub_id, seq = msg.payload.decode().split(":")
key, seq = (msg.topic, pub_id), int(seq)
stats["received"] += 1
stats["dup_flag"] += msg.dup
if seq in seen[key]:
stats["duplicates"] += 1
return
seen[key].add(seq)
if seq < last[key]:
stats["out_of_order"] += 1
print(f"OUT OF ORDER {key}: {seq} after {last[key]}")
last[key] = max(last[key], seq)
def missing():
return sum(last[k] + 1 - len(seen[k]) for k in seen)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
if os.getenv("MQTT_USER"):
client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
client.on_connect, client.on_message = on_connect, on_message
client.connect(HOST, 1883)
client.loop_start()
try:
while True:
time.sleep(5)
print(dict(stats), "missing:", missing())
except KeyboardInterrupt:
print("FINAL", dict(stats), "missing:", missing())import os, sys
import paho.mqtt.client as mqtt
HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
RUN, N = sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 10000
PUB_ID = os.getenv("PUB_ID", "p1")
TOPICS = int(os.getenv("TOPICS", "4"))
QOS = int(os.getenv("MQTT_QOS", "1"))
pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
if os.getenv("MQTT_USER"):
pub.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
pub.max_inflight_messages_set(int(os.getenv("INFLIGHT", "20")))
pub.connect(HOST, 1883)
pub.loop_start()
seq, infos = [0] * TOPICS, []
for i in range(N):
t = i % TOPICS
infos.append(pub.publish(f"testmqtt/order/{RUN}/t{t}", f"{PUB_ID}:{seq[t]}", qos=QOS))
seq[t] += 1
for info in infos:
info.wait_for_publish(timeout=60)
pub.disconnect()
pub.loop_stop()
print(f"published {N} messages on {TOPICS} topics as {PUB_ID}")pip install "paho-mqtt>=2"
python order_sub.py run42 # terminal 1, start first
python order_pub.py run42 10000 # terminal 2On a healthy broker and a stable network, the baseline should end with out_of_order: 0, duplicates: 0 and missing: 0. Before scripting, you can watch the stream live by subscribing to testmqtt/order/# in the online MQTT client while the publisher runs.
Test 2: provoke the edge cases
Change one variable at a time and compare with the baseline:
| Experiment | How | What you should see |
|---|---|---|
| Two publishers | Run order_pub.py twice in parallel with PUB_ID=p1 and PUB_ID=p2 | Each publisher's stream stays ordered; they interleave |
| QoS 0 under load | MQTT_QOS=0, larger N, several publishers | Possibly missing > 0, still no out-of-order |
| Network cut | Block the publisher's port 1883 for a few seconds mid-run (see QoS with a network cut) | Duplicates and dup_flag counts; late re-sends possible with a persistent session |
| One in flight | INFLIGHT=1 plus a network cut | Duplicates possible, but no message after its successor |
| Shared subscription | Subscribe two order_sub.py copies to $share/g/testmqtt/order/run42/# | Each copy sees gaps — the other member got those messages |
For the shared-subscription run, change the subscribe call to the $share/... filter; you can check that a filter matches your topics with the topic matcher. Duplicates are not a broker bug — they are QoS 1 doing its job; duplicate messages covers how to make consumers idempotent.
Don't lose order in your own code
Most "MQTT reordered my messages" reports come from the consumer. In Node.js, an async message handler does not wait for the previous message:
client.on("message", async (topic, payload) => {
await saveToDb(topic, payload); // message 2 can finish before message 1
});Chain the work per topic to keep per-topic order while still processing different topics in parallel:
const chains = new Map();
client.on("message", (topic, payload) => {
const prev = chains.get(topic) ?? Promise.resolve();
const next = prev.then(() => saveToDb(topic, payload)).catch(console.error);
chains.set(topic, next);
});The same applies to thread pools in Python or Java and to worker queues downstream of the broker: partition work by topic or device ID, never round-robin.
Designing for order
- Put events that must stay ordered on one topic from one publisher — for example
site/device42/events, not one topic per event type. - Add a sequence number or source timestamp to the payload, so consumers can drop stale updates ("last writer wins") or reorder small windows.
- Make handlers idempotent, because QoS 1 will deliver duplicates.
- Treat shared subscriptions as unordered by design; partition by device if order matters.
- Use retained messages for "current state", where only the latest value counts and order matters less.
MQTT's ordering guarantee is real but narrow: one publisher, one topic, one QoS. Ten minutes with a sequence-number test shows you how your broker and network behave, and the variants show you exactly where your design must take over — usually with a sequence number in the payload and a consumer that processes each topic in order.
Frequently asked questions
Does MQTT guarantee message order?
Partly. The specification requires a broker to deliver messages from one publisher on the same topic and QoS in the order it received them. There is no ordering guarantee across topics, across different publishers, or between members of a shared subscription.
Can QoS 1 messages arrive out of order?
Yes, in one specific way: after a reconnect, a retransmitted QoS 1 message can arrive after later messages, for example 1, 2, 3, 2, 3, 4. The final copies are in order, but duplicates may appear late. Keeping one message in flight at a time avoids it.
How do I keep MQTT messages in order across topics?
MQTT cannot do it for you. Put related events on one topic from one publisher, or add a sequence number or timestamp to the payload and let the consumer reorder or discard stale messages.


