Skip to content

Point-to-Point Pattern — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Point. We cover key concepts, practical examples, and best practices to help you master this topic.

Point-to-point messaging delivers each message to exactly one consumer from a queue, ideal for task distribution where work must not be duplicated across multiple workers.

What You'll Learn

By the end of this lesson, you will understand the point-to-point pattern, when to use it, how it differs from pub-sub, and how to implement it with RabbitMQ and competing consumers.

Why It Matters

Many real-world workloads involve distributing tasks across workers where each task must be processed exactly once. Sending a welcome email, resizing an image, charging a payment — these should never be duplicated. The point-to-point pattern guarantees single-consumer delivery.

Real-World Use

A document processing service receives PDF uploads. Each PDF must be converted to text, indexed for search, and archived. Uploads arrive faster than a single worker can Process them. A queue holds the tasks, and multiple worker instances each grab the next available PDF. No PDF is processed by two workers, and no PDF is missed.

How Point-to-Point Works

flowchart LR
    P[Producer] --> Q[Queue]
    Q --> C1[Consumer 1]
    Q --> C2[Consumer 2]
    Q --> C3[Consumer 3]
    style Q fill:#f90,color:#fff

The producer sends messages to a queue. Multiple consumers read from the same queue, but each message is delivered to exactly one consumer. This is the competing consumers pattern.

Point-to-Point vs Pub-Sub

Feature Point-to-Point Publish-Subscribe
Delivery One consumer All subscribers
Queue type Work queue Topic / fanout
Use case Task distribution Event broadcasting
Scaling Add consumers for parallelism Add subscribers for new features
Duplication Prevented by design Required by design

Implementing with RabbitMQ

import pika
import json

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='tasks', durable=True)

channel.basic_publish(
    exchange='',
    routing_key='tasks',
    body=json.dumps({'task': 'resize_image', 'file': 'photo.jpg'}),
    properties=pika.BasicProperties(delivery_mode=2)
)

print("Task published to queue 'tasks'")
connection.close()

Expected output:

Task published to queue 'tasks'
import pika
import json
import time

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

channel.queue_declare(queue='tasks', durable=True)
channel.basic_qos(prefetch_count=1)

def callback(ch, method, properties, body):
    task = json.loads(body)
    print(f"Processing: {task['task']} for {task['file']}")
    time.sleep(1)
    print(f"Completed: {task['task']}")
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_consume(queue='tasks', on_message_callback=callback)
print("Waiting for tasks. Press Ctrl+C to exit.")
channel.start_consuming()

Expected output:

Waiting for tasks. Press Ctrl+C to exit.
Processing: resize_image for photo.jpg
Completed: resize_image

The consumer sets prefetch_count=1 so RabbitMQ does not send more than one message at a time. This ensures fair distribution across multiple consumers.

Why the Queue Guarantees Single Delivery

The queue holds messages in order. When a consumer requests a message, the broker dequeues it and marks it as delivered but not yet acknowledged. If the consumer crashes before acknowledging, the broker re-queues the message so another consumer can pick it up.

This is why you never lose messages, but you might process a message twice if the consumer crashes after processing but before acknowledging. Your application must handle this by making tasks idempotent.

Common Mistakes

1. Forgetting to Set Prefetch Count

Without prefetch, RabbitMQ sends all messages to the first available consumer. Other consumers stay idle while one consumer is overloaded. Always set prefetch_count=1 for fair distribution.

2. Using Auto-Acknowledgement

With auto_ack=True, the broker removes the message as soon as it is sent. If the consumer crashes mid-processing, the message is lost forever. Always use manual acknowledgments for critical tasks.

3. Assuming Strict Ordering with Multiple Consumers

If you run three consumers, message 1 might go to consumer A, message 2 to consumer B. Consumer B finishes first, so message 2 is processed before message 1. Do not rely on FIFO ordering with multiple consumers.

4. Not Making Tasks Idempotent

The same task may be delivered twice if the consumer crashes after processing but before acknowledging. Design tasks so running them twice produces the same result as running them once.

5. Ignoring Poison Messages

A message that always causes an error (poison message) is repeatedly delivered and repeatedly fails. Implement a dead-letter queue with a max retry count to quarantine problematic messages.

Practice Questions

1. How does point-to-point differ from publish-subscribe?

Point-to-point delivers each message to exactly one consumer. Publish-subscribe delivers each message to all subscribed consumers. Point-to-point is for task distribution; pub-sub is for event broadcasting.

2. What happens if all consumers are busy?

Messages remain in the queue until a consumer becomes available. The queue depth increases. When a consumer finishes its current task, it picks up the next waiting message.

3. How do you ensure fair distribution across consumers?

Set prefetch_count=1 on the consumer channel. This tells the broker to send only one message at a time to each consumer, preventing one consumer from hoarding messages.

4. Can a point-to-point queue have multiple producers?

Yes. Multiple producers can send to the same queue. Each message is stored independently and delivered to exactly one consumer regardless of which producer sent it.

Challenge

Design a video transcoding system: videos are uploaded and queued. Ten worker instances each grab the next video. Transcoding takes 1-10 minutes per video. Implement prefetch, manual acknowledgments, and a dead-letter queue for videos that fail more than three times.

FAQ

What broker supports point-to-point?

All major brokers support point-to-point. In RabbitMQ, it is a named queue with competing consumers. In Kafka, it is a consumer group sharing a topic. In SQS, every queue is inherently point-to-point.

Can a message be delivered to multiple consumers?

Not in pure point-to-point. If you need multiple consumers to each receive the same message, use publish-subscribe instead.

What happens to unacknowledged messages on shutdown?

When the consumer connection drops, RabbitMQ re-queues all unacknowledged messages. They are redelivered to another consumer or to the same consumer when it reconnects.

How many consumers should I run per queue?

Monitor queue depth and consumer utilization. If the queue depth grows, add consumers. If consumers are idle most of the time, reduce them. A good starting point is 2-4 consumers per queue.

Does point-to-point guarantee order?

With a single consumer, yes. With multiple consumers, no. If order matters, use a single consumer or partition messages by key (Kafka).

Mini Project: Task Queue with Multiple Workers

import pika
import json
import threading
import time
import random

def start_worker(worker_id):
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='work_tasks', durable=True)
    channel.basic_qos(prefetch_count=1)

    def callback(ch, method, properties, body):
        task = json.loads(body)
        print(f"[Worker {worker_id}] Processing task {task['id']}: {task['type']}")
        time.sleep(random.uniform(0.5, 2.0))
        print(f"[Worker {worker_id}] Completed task {task['id']}")
        ch.basic_ack(delivery_tag=method.delivery_tag)

    channel.basic_consume(queue='work_tasks', on_message_callback=callback)
    channel.start_consuming()

for i in range(3):
    t = threading.Thread(target=start_worker, args=(i,), daemon=True)
    t.start()

time.sleep(1)

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='work_tasks', durable=True)

for i in range(10):
    task = {'id': i, 'type': random.choice(['encode', 'thumbnail', 'watermark'])}
    channel.basic_publish(
        exchange='',
        routing_key='work_tasks',
        body=json.dumps(task),
        properties=pika.BasicProperties(delivery_mode=2)
    )
    print(f"[Producer] Queued task {task['id']}: {task['type']}")

connection.close()
time.sleep(5)

Expected output:

[Producer] Queued task 0: encode
[Worker 0] Processing task 0: encode
[Producer] Queued task 1: thumbnail
[Worker 1] Processing task 1: thumbnail
[Producer] Queued task 2: watermark
[Worker 2] Processing task 2: watermark
...
[Worker 0] Completed task 0: encode
[Worker 0] Processing task 3: thumbnail

What's Next

Now that you understand point-to-point, explore the publish-subscribe pattern for broadcasting events to multiple consumers, or dive into message broker concepts for a deeper understanding of how brokers route messages.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro