The ZeroMQ PAIR socket type is designed for exclusively one-to-one communication, meaning a PAIR socket can only be connected to one other socket at any given time.

Let’s see it in action. Imagine a simple scenario where a worker process needs to send a status update to a supervisor process.

Worker (sender.py):

import zmq
import time

context = zmq.Context()
socket = context.socket(zmq.PAIR)
socket.connect("tcp://localhost:5555")

print("Worker connected to supervisor...")

for i in range(5):
    message = f"Status update {i}"
    print(f"Worker sending: {message}")
    socket.send_string(message)
    time.sleep(1)

socket.close()
context.term()

Supervisor (receiver.py):

import zmq

context = zmq.Context()
socket = context.socket(zmq.PAIR)
socket.bind("tcp://*:5555")

print("Supervisor listening on port 5555...")

for i in range(5):
    message = socket.recv_string()
    print(f"Supervisor received: {message}")

socket.close()
context.term()

If you run receiver.py first, then sender.py, you’ll see the messages flowing seamlessly:

Supervisor listening on port 5555...
Worker connected to supervisor...
Worker sending: Status update 0
Supervisor received: Status update 0
Worker sending: Status update 1
Supervisor received: Status update 1
...

The PAIR socket type is ideal for scenarios where you need a dedicated, two-way communication channel between two specific processes, like a master controlling a single worker, or a client interacting with a unique service instance. Unlike other socket types such as REQ/REP or PUB/SUB, PAIR enforces this strict one-to-one relationship at the ZeroMQ level. If a PAIR socket is already connected, attempting to connect another will result in an error. This exclusivity simplifies management and guarantees that messages sent will always arrive at their intended single destination.

The mental model for PAIR is that of a direct, unshared telephone line. Once you dial a number and establish a connection, only you and the person on the other end are on that line. No one else can join, and you can’t simultaneously be on another call with a different party using the same phone. This is enforced by ZeroMQ’s internal state for PAIR sockets. When socket.connect() is called, ZeroMQ records the peer’s endpoint. If another connect() or bind() (depending on which side you are) is attempted on an already established PAIR socket, ZeroMQ will detect the conflict and prevent the operation.

The core of PAIR’s functionality lies in its simplicity and its guarantee of a single, reliable conduit. It’s often used as a building block for more complex patterns or for straightforward, persistent connections. The connect operation on the client side and bind on the server side are the primary control levers. The client initiates the connection to a specific endpoint, while the server listens on an address. Messages are then sent and received bidirectionally.

What many overlook is that the PAIR socket is bidirectional by default. While often conceptualized as a master-worker or client-server, it’s truly just two endpoints talking. You can send from both sides, and both sides can receive. The send and recv operations work on either end of the established connection, enabling full-duplex communication without any additional configuration.

The next logical step after mastering PAIR’s direct communication is exploring how to manage multiple such connections, which leads into the ROUTER/DEALER socket types.

Want structured learning?

Take the full Zeromq course →