Most developers learn REST first, and for good reason. The mental model maps cleanly onto how we already think about communication: you ask a question, you get an answer. The client initiates, the server responds, the interaction is complete. Request/response mirrors conversation — it's intuitive.

I understood MQTT conceptually before I built with it. I knew it used a broker, I knew it was publish/subscribe, I knew it was popular in IoT. What I didn't understand — and couldn't, from reading — was how fundamentally different the mental model is once you're actually designing around it. Not better or worse than REST in any abstract sense, but different in ways that change what your system can do and how you think about it.

The Connection Is the State

In REST, connections are transient by design. A client opens a connection, completes a request, and closes it. The server doesn't know or care whether the client still exists between requests. This is a feature — it's what makes REST stateless and horizontally scalable.

MQTT inverts this. A connected device is an available device. The connection itself is meaningful state, and the broker tracks it. At any point, the server needs to know whether a device is reachable before sending it a command. In REST, you'd find out by sending the request and waiting for a timeout. In MQTT, you already know: if the device is subscribed, it's reachable.

MQTT formalizes this further with a feature called the Last Will and Testament. Before a client connects, it tells the broker what message to publish on its behalf if the connection drops unexpectedly. The broker fires this automatically if the client vanishes without a clean disconnect — a power cut, a network failure. In REST, you'd simulate this with a heartbeat endpoint and a timeout loop. MQTT handles it natively, at the protocol level.

You Design Topics, Not Endpoints

In REST, the API surface is a set of endpoints. Each represents a resource or action. The design question is: what nouns should exist, and what verbs should apply to them?

In MQTT, the equivalent surface is topic hierarchy — slash-delimited strings whose structure determines everything about what the system can express and who can participate in it. Topic design isn't just naming; the topic is the address, the channel, and the access control boundary simultaneously.

Getting it right means thinking about data flow direction — which topics carry data from device to cloud, which carry commands from cloud to device — and about scope, because a topic that addresses all devices is structurally different from one that addresses a specific device by identifier. A poorly designed REST API gives you awkward URLs. A poorly designed MQTT topic hierarchy gives you a system where the wrong subscribers receive the wrong messages, or where you can't enforce access boundaries without rewriting the entire addressing scheme.

Fan-Out Is Free

In REST, if three services need to react to the same event, you either call all three explicitly or build a gateway that fans out for you. Someone has to know who the consumers are. The publisher is coupled to its audience.

In MQTT, a publisher puts a message on a topic and stops caring. Any number of subscribers can be listening. None of them need to coordinate, and none need to be known to the publisher in advance. Adding a new consumer means subscribing to the existing topic — no changes required anywhere else in the system.

This changes how you think about extending functionality. A new process that needs to react to an existing data stream simply subscribes and begins receiving it. The existing system has no knowledge of and no coupling to the new one. In REST, adding a consumer typically means modifying something that was already working. In pub/sub, it means subscribing. That asymmetry compounds over the life of a system.

Commands Feel Different When They're Not Requests

Sending a command to a remote device over REST requires the device to poll. The server can't push — the device has no address the server can reach directly. So the device asks, repeatedly: "is there anything for me?" The polling interval is a permanent tradeoff between latency and load.

MQTT eliminates that tradeoff. The device maintains a persistent subscription to a command topic. When the server publishes a command, it arrives at the device the moment it's sent. No polling. No latency floor set by an interval.

There's also a subtler design difference. In REST, a command is a request: the server asks for something and waits for confirmation. In MQTT, a command is a published event: the server announces that something should happen, and the device acts on it if connected and subscribed. The broker doesn't wait. Whether the device acknowledged the command, and how to handle the case where it didn't, is your problem to solve explicitly. This is more work than REST's implicit confirmation model — and more honest about what's actually happening when you send a message to a device that might be offline.

Quality of Service Is a Per-Message Decision

REST doesn't ask you to think about delivery guarantees. You send a request, you either get a response or an error, and the transport layer handles the mechanics. Delivery semantics are baked into the protocol.

MQTT surfaces this as an explicit, per-message choice. Each message is published at one of three quality-of-service levels: fire and forget, at-least-once delivery, or exactly-once delivery. Higher guarantees cost more — in latency, in broker storage, in round-trips. You choose based on what the message is and what happens if it's lost or duplicated.

For a system that mixes high-frequency telemetry with low-frequency commands, this isn't theoretical. Sensor readings that arrive many times per minute are reasonable candidates for fire-and-forget — any individual reading is low value, and the overhead of guaranteed delivery across thousands of messages per hour adds up. Commands that affect physical state are the opposite: the cost of guaranteed delivery is worth paying. The same device, on the same connection, uses different delivery semantics for different message types — and MQTT makes that a natural design decision rather than an afterthought.

REST Is Still the Right Answer — Sometimes

Understanding MQTT well enough to build with it also clarified when not to use it.

MQTT suits data that flows continuously in one direction, events that need to reach multiple consumers, and systems where connection state is meaningful. It is not suited to explicit queries — "give me this device's history for the last seven days" — or to administrative operations that are inherently request/response in nature.

The more useful outcome wasn't learning that MQTT is better. It was developing a clear enough model of what each protocol actually does to know, before writing any code, which one fits the problem. Real systems often use both: MQTT for the real-time data plane, REST for the management plane. Reaching for MQTT everywhere would be as wrong as never reaching for it at all.

What Actually Changed

Before building with MQTT, my instinct when designing any system interaction was request/response. Something needs data, so it asks. Something needs to notify another service, so it calls it. The mental model was conversational, bilateral, explicit.

Working with a system where devices maintain live connections, receive commands without polling, and publish continuous data streams to multiple consumers simultaneously broke that instinct in a useful way. Some interactions are not conversations. Some data flows in one direction continuously, and the infrastructure should reflect that rather than simulate it with polling loops and callback endpoints.

Request/response is intuitive — that's why it comes first. Pub/sub is powerful in the specific cases where the request/response model is fighting the problem rather than solving it. Recognising those cases, rather than defaulting to the familiar model, is the thing that actually stuck.