Articles
ZeroMQ Interview Questions: Socket Patterns, Backpressure, and Delivery Guarantees
Explain ZeroMQ socket patterns, high-water marks, REQ timeouts, lost replies, thread ownership, and application delivery guarantees in interviews.
A ZeroMQ send call succeeds, the worker crashes, and the caller asks whether the job finished. The send result alone cannot answer. A useful interview explanation separates local queue acceptance, peer receipt, and application completion before choosing a retry policy.
ZeroMQ interview questions become concrete when you trace a socket pattern under startup, overload, disconnect, and shutdown. This article uses an original pressure table and lost-reply timeline to connect those events to the application's delivery contract.
Official facts are linked to libzmq manuals, ZeroMQ pattern specifications, and the official Guide. Examples and application designs are original practice or inference, not candidate reports or employer-specific interview claims. The examples focus on established PUB/SUB, PUSH/PULL, REQ/REP, and DEALER/ROUTER patterns. For broader practice, use Design a Simple Message Queue Service Without an Off-the-Shelf Broker.
What does a successful send actually prove?
Official fact: zmq_send queues a message. Success does not establish that it has reached the network, been received by a peer, or been processed by the application.
State the acknowledgment boundary in ordinary language: “The local socket accepted the send according to its semantics.” Then ask what evidence establishes the next boundary. A peer receiving bytes is different from a worker committing an output file or database change.
Consider an original image-rendering job. The client submits operation render-42. The worker receives it, writes the final artifact, and replies with its location. A client-side send success says nothing about whether the artifact exists. Even a worker acknowledgment is useful only if the protocol defines when it is emitted.
If the worker acknowledges immediately on receipt, the client knows the request reached that worker. If it acknowledges after a durable job record, the guarantee is different. If it replies only after the completed artifact is committed, the response covers completion under the storage assumptions you named.
Do not describe TCP reliability as a complete business guarantee. It does not make an in-memory queue durable across a process crash or coordinate a database mutation with the caller's receipt of a reply. The transport can be working correctly while the application cannot distinguish completed work from lost work.
This distinction should come before “at least once” or “exactly once.” Those labels need a scope, failure model, and acknowledgment contract. Without them, the phrase may describe an intention rather than a property of the design.
Which socket pattern matches the communication contract?
Official facts: the pipeline specification defines PUSH/PULL work distribution; the request-reply specification defines REQ/REP and the asynchronous DEALER/ROUTER building blocks; the publish-subscribe specification defines fan-out behavior.
Use this decision table for the named patterns:
| Pattern | Intended routing | Important pressure or state behavior | Application question |
|---|---|---|---|
| PUB → SUB | Fan out matching publications | A slow subscriber can miss messages; PUB does not block for that subscriber's full queue | Is loss acceptable, or is replay required? |
| PUSH → PULL | Distribute work among available peers | Block or return a nonblocking error when no peer can accept more work | How is worker completion reported? |
| REQ ↔ REP | Request followed by reply | Default REQ alternates send and receive | What happens after a reply timeout? |
| DEALER ↔ ROUTER | Asynchronous exchanges with explicit routing support | Application protocol must correlate requests, replies and destinations | How are retries and stale replies identified? |
A PUSH message is assigned to one available PULL peer; it is not a broadcast to all workers. Conversely, PUB/SUB is not a durable work queue merely because several subscribers happen to consume publications.
Suppose the image renderer needs each job processed by one worker and a completion result returned to its caller. PUSH/PULL can distribute work, but the return path and ownership policy still need a design. DEALER/ROUTER may be a better building block when the protocol needs concurrent request/reply exchanges with correlation.
The answer should defend the contract, not declare one pattern universally superior. For a live dashboard where a newer update supersedes an older one, lossy publication may be acceptable. For an invoice operation, silently missing a message is a different product outcome.
Why does a slow subscriber miss messages?
Official fact: the socket manual states that PUB drops messages for a subscriber whose queue reaches its high-water mark; PUB sends do not block on that condition. A SUB socket initially has no subscriptions until configured.
For an original exercise, one publisher sends numbered observations to subscribers Fast and Slow. Slow stops consuming while Fast continues. Predict a gap in Slow's sequence under sufficient pressure without assuming Fast must see the same gap.
This is why “the publisher successfully sent message 500” is not proof that every subscriber received it. Observe sequence numbers per subscriber. A reconnect also does not imply automatic historical replay of every missed publication.
Startup has a separate race. The publisher may begin before a subscriber's connection and subscription are effective. Sleeping for a guessed interval can make a test pass on one machine while leaving the protocol undefined. If the application requires a known starting point, design a readiness handshake or snapshot-and-update protocol appropriate to that requirement.
A snapshot design must handle ordering. If a subscriber fetches current state and then subscribes, updates during the gap can be missed. If it subscribes first and buffers updates, the snapshot needs a sequence boundary so buffered messages can be applied without duplicating or skipping changes.
Those are application-level design recommendations. ZeroMQ does not turn a sequence number you invent into durable replay storage. Decide where the snapshot, retained updates, and recovery cursor live, and how long recovery remains possible.
How do high-water marks bound pressure?
Official fact: ZMQ_SNDHWM limits outstanding messages per peer, measured in messages rather than bytes. The action on pressure depends on the socket type, and observed acceptance counts can differ from a simple threshold estimate.
In an original sizing estimate, a per-peer queue target of 1000 messages with a typical 64 KiB payload implies roughly 62.5 MiB of payload per peer. Ten peers suggest roughly 625 MiB before counting message metadata, transport buffers, variability, or other queues. This is an estimate, not a process-memory upper bound.
Now make payloads range from 1 KiB to 4 MiB. The same message-count limit admits very different memory use. If memory is the requirement, enforce payload size limits and measure the complete pipeline instead of relying on a single socket option.
For PUSH, an unavailable or full peer can affect which other peers remain eligible. When none can accept work, a blocking send waits; a nonblocking send can return EAGAIN. The pipeline specification explicitly separates this behavior from dropping a message that cannot be queued.
Handle EAGAIN as a pressure signal. Decide whether to wait within a deadline, reject new work, or place it in a deliberately bounded application queue. An unbounded retry buffer simply moves the overload into your own process.
Do not equate work distribution with equal completion rates. A slow worker may accumulate work already assigned to it. If task durations vary substantially, an application protocol with worker readiness or credits can express capacity more accurately than assuming every worker finishes equally quickly.
Why does REQ produce a state-machine error after a timeout?
Official fact: default REQ uses a send-then-receive sequence. A timeout while waiting for the reply does not, by itself, complete that sequence. An operation attempted in the wrong socket state can produce EFSM; the send manual documents that error.
In an original trace, the client sends render-42, waits for a reply, times out, and immediately sends again on the same default REQ socket. Explain both problems: the socket may reject the next send, and the first request may already have completed at the worker.
The official Guide's reliable request-reply chapter develops retry patterns, including reconnecting request sockets. Recreating a socket can address the local state-machine issue, but it cannot undo an already executed operation.
If you use options that relax REQ behavior, read their correlation requirements and the exact libzmq version's contract. Relaxation is not a substitute for deciding how old replies relate to newer attempts. Similarly, switching to DEALER/ROUTER removes the simple lock-step model but makes the application's correlation protocol more important.
A useful explanation names two identifiers: a stable logical operation ID and, if needed, an attempt ID. Retries of the same operation preserve the first identifier. The attempt ID helps diagnose which exchange produced a response without redefining the business action.
How do you recover from a lost reply safely?
Follow this original completion timeline:
| Step | Worker state | Client knowledge |
|---|---|---|
1. Client sends render-42 | Request may be queued or in transit | Local send accepted |
| 2. Worker commits artifact and completion record | Operation finished under the selected storage contract | No completion evidence yet |
| 3. Reply is lost during failure | Completion record still exists | Timeout; outcome unknown |
4. Client retries render-42 | Worker checks the same logical ID | Can receive the recorded result rather than duplicate work |
The recovery design requires more than putting an ID in the payload. The worker needs an authoritative record binding that ID to the request's meaning and result. If the same ID arrives with different inputs, reject the mismatch rather than return an unrelated artifact.
Concurrent retries also need serialization. Two workers must not both observe “absent” and independently execute the operation. Use an atomic claim or another explicit coordination mechanism appropriate to the storage system. Explain what happens if the claimant dies and whether ownership can be recovered safely.
The completion record and side effect must be coordinated. If an artifact is committed but its operation record is missing after a crash, a retry may repeat expensive work. If the record says complete before the artifact is durable, the caller may receive a nonexistent result. Define the order and recovery policy for that gap.
External effects may need downstream idempotency or reconciliation. A local record cannot make an unrelated external service participate in your transaction. Describe the remaining uncertainty instead of promising universal exactly-once execution.
Also state the record's retention period. After deduplication state expires, replaying an old ID may no longer have the same guarantee. Retry deadlines and deduplication retention belong in the same protocol discussion.
Who owns the socket, and how does shutdown finish?
Official fact: established socket types discussed here must not be shared across threads as if they were ordinary thread-safe queues. The socket manual distinguishes specific thread-safe types from others. A shared context does not make every socket thread-safe.
A practical design uses one owning thread per such socket and an explicit handoff mechanism for other threads. For an event loop, keep message handling bounded so one long task does not stop every other request from being serviced. Moving slow work elsewhere still requires ownership and result-routing rules.
Official fact: ZMQ_LINGER controls how pending outbound messages are treated after closing or disconnecting a socket and affects context termination. A zero linger discards pending messages immediately; a finite value bounds waiting. That setting controls shutdown waiting; application completion needs separate evidence.
For the renderer, stop accepting new work, track outstanding operation IDs, and choose a deadline for completion or recovery handoff. Then close sockets according to the documented ownership model. Do not block indefinitely hoping an unreachable peer will acknowledge.
Test shutdown with queued work and an unavailable peer. Verify which jobs have durable records and which callers receive uncertainty or rejection. A process exiting cleanly is not sufficient evidence that every queued request finished.
Practice the messaging contract
| Question | What to explain |
|---|---|
| Design a Simple Message Queue Service Without an Off-the-Shelf Broker | Separate in-memory routing from durable ownership. |
| Explain Kafka and Message-Queue Delivery Semantics | Name the scope of each delivery guarantee. |
| Design High-Throughput Messaging Infrastructure | Bound payloads, queues and downstream work. |
| Design a Multi-Channel Message Queue | Define routing and acknowledgment per channel. |
| Design a thread-safe bounded queue | Explain pressure and thread ownership explicitly. |
Use the message-queue design exercise to trace a full queue, a worker crash, and a lost reply. State exactly what the caller knows at each boundary.