# Message Queue Pattern

## Problem

When a producer generates work that needs to be processed, calling the consumer directly creates tight coupling and means the producer blocks until processing completes. If the consumer is slow or down, the producer fails too. And if there's more work than one consumer can handle, there's no way to distribute load.

## Solution

Place a **queue** between producers and consumers. Producers enqueue messages and continue immediately. Consumers dequeue messages and process them independently. Key properties:

- **Point-to-point** — Each message is delivered to exactly one consumer.
- **Competing consumers** — Multiple consumers can read from the same queue to scale horizontally.
- **Durability** — Messages persist in the queue until acknowledged.
- **Decoupling** — Producer and consumer don't need to be alive at the same time.

## When to Use

- Distributing work across multiple worker processes
- Smoothing traffic spikes (queue absorbs bursts)
- Background job processing (emails, reports, image processing)
- When producer throughput exceeds consumer throughput
- Cross-service async communication

## When to Avoid

- When you need immediate responses (use request-reply)
- When all consumers need to see every message (use pub-sub)
- Simple in-process function calls
- When message ordering is critical and can't be partitioned

## Pseudocode

```
class MessageQueue:
    queue = FIFO buffer

    function enqueue(message):
        queue.push(message)

    function dequeue():
        message = queue.pop()
        return message

    function acknowledge(message):
        mark message as processed
```

## Python Implementation

```python
import threading
import time
import queue
from dataclasses import dataclass, field
from typing import Any


@dataclass
class Message:
    id: int
    body: Any
    attempts: int = 0


class MessageQueue:
    def __init__(self, name: str, max_size: int = 100):
        self.name = name
        self._queue: queue.Queue[Message] = queue.Queue(maxsize=max_size)
        self._next_id = 0
        self._lock = threading.Lock()
        self.enqueued = 0
        self.processed = 0

    def enqueue(self, body: Any) -> Message:
        with self._lock:
            self._next_id += 1
            msg = Message(id=self._next_id, body=body)
            self.enqueued += 1
        self._queue.put(msg)
        return msg

    def dequeue(self, timeout: float = 1.0):
        try:
            msg = self._queue.get(timeout=timeout)
            msg.attempts += 1
            return msg
        except queue.Empty:
            return None

    def acknowledge(self, msg: Message):
        with self._lock:
            self.processed += 1

    @property
    def size(self):
        return self._queue.qsize()


# --- Demo ---
mq = MessageQueue("tasks")
stop_event = threading.Event()
results = []
results_lock = threading.Lock()


def producer(name: str, items: list):
    for item in items:
        msg = mq.enqueue(item)
        print(f"  [{name}] Enqueued msg#{msg.id}: {item}")
        time.sleep(0.02)


def consumer(name: str):
    while not stop_event.is_set():
        msg = mq.dequeue(timeout=0.2)
        if msg is None:
            continue
        # Process
        time.sleep(0.05)
        mq.acknowledge(msg)
        with results_lock:
            results.append((name, msg.id, msg.body))
        print(f"  [{name}] Processed msg#{msg.id}: {msg.body}")


# Start 2 consumers (competing consumers)
consumers = [
    threading.Thread(target=consumer, args=(f"Consumer-{i}",), daemon=True)
    for i in range(1, 3)
]
for c in consumers:
    c.start()

# Produce messages
print("=== Producing messages ===")
producer("Producer", [f"task-{i}" for i in range(1, 7)])

# Wait for processing
time.sleep(1)
stop_event.set()
for c in consumers:
    c.join(timeout=1)

print()
print("=== Results ===")
for worker, msg_id, body in sorted(results, key=lambda x: x[1]):
    print(f"  msg#{msg_id} ({body}) → processed by {worker}")

print()
print(f"Enqueued: {mq.enqueued}, Processed: {mq.processed}, Remaining: {mq.size}")
```

**Output (consumer assignment may vary):**
```
=== Producing messages ===
  [Producer] Enqueued msg#1: task-1
  [Consumer-1] Processed msg#1: task-1
  [Producer] Enqueued msg#2: task-2
  [Consumer-2] Processed msg#2: task-2
  [Producer] Enqueued msg#3: task-3
  [Producer] Enqueued msg#4: task-4
  [Consumer-1] Processed msg#3: task-3
  [Consumer-2] Processed msg#4: task-4
  [Producer] Enqueued msg#5: task-5
  [Producer] Enqueued msg#6: task-6
  [Consumer-1] Processed msg#5: task-5
  [Consumer-2] Processed msg#6: task-6

=== Results ===
  msg#1 (task-1) → processed by Consumer-1
  msg#2 (task-2) → processed by Consumer-2
  msg#3 (task-3) → processed by Consumer-1
  msg#4 (task-4) → processed by Consumer-2
  msg#5 (task-5) → processed by Consumer-1
  msg#6 (task-6) → processed by Consumer-2

Enqueued: 6, Processed: 6, Remaining: 0
```

## Go Implementation

```go
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

type Message struct {
	ID   int
	Body string
}

type MessageQueue struct {
	ch        chan Message
	nextID    int64
	processed int64
}

func NewMessageQueue(size int) *MessageQueue {
	return &MessageQueue{ch: make(chan Message, size)}
}

func (mq *MessageQueue) Enqueue(body string) Message {
	id := int(atomic.AddInt64(&mq.nextID, 1))
	msg := Message{ID: id, Body: body}
	mq.ch <- msg
	return msg
}

func (mq *MessageQueue) Dequeue() (Message, bool) {
	select {
	case msg := <-mq.ch:
		return msg, true
	case <-time.After(200 * time.Millisecond):
		return Message{}, false
	}
}

func (mq *MessageQueue) Ack() {
	atomic.AddInt64(&mq.processed, 1)
}

func main() {
	mq := NewMessageQueue(100)
	var wg sync.WaitGroup
	done := make(chan struct{})

	// Consumers
	for i := 1; i <= 2; i++ {
		wg.Add(1)
		go func(name string) {
			defer wg.Done()
			for {
				select {
				case <-done:
					return
				default:
				}
				msg, ok := mq.Dequeue()
				if !ok {
					select {
					case <-done:
						return
					default:
						continue
					}
				}
				time.Sleep(50 * time.Millisecond)
				mq.Ack()
				fmt.Printf("  [%s] Processed msg#%d: %s\n", name, msg.ID, msg.Body)
			}
		}(fmt.Sprintf("Consumer-%d", i))
	}

	// Producer
	fmt.Println("=== Producing messages ===")
	for i := 1; i <= 6; i++ {
		msg := mq.Enqueue(fmt.Sprintf("task-%d", i))
		fmt.Printf("  [Producer] Enqueued msg#%d: %s\n", msg.ID, msg.Body)
		time.Sleep(20 * time.Millisecond)
	}

	time.Sleep(1 * time.Second)
	close(done)
	wg.Wait()

	fmt.Printf("\nProcessed: %d\n", atomic.LoadInt64(&mq.processed))
}
```

## JavaScript Implementation

```javascript
class MessageQueue {
  constructor(name) {
    this.name = name;
    this._queue = [];
    this._nextId = 0;
    this.enqueued = 0;
    this.processed = 0;
    this._waiters = [];
  }

  enqueue(body) {
    this._nextId++;
    const msg = { id: this._nextId, body, attempts: 0 };
    this.enqueued++;

    if (this._waiters.length > 0) {
      const resolve = this._waiters.shift();
      msg.attempts++;
      resolve(msg);
    } else {
      this._queue.push(msg);
    }
    return msg;
  }

  async dequeue(timeoutMs = 1000) {
    if (this._queue.length > 0) {
      const msg = this._queue.shift();
      msg.attempts++;
      return msg;
    }
    return new Promise((resolve) => {
      const timer = setTimeout(() => {
        const idx = this._waiters.indexOf(resolve);
        if (idx !== -1) this._waiters.splice(idx, 1);
        resolve(null);
      }, timeoutMs);

      this._waiters.push((msg) => {
        clearTimeout(timer);
        resolve(msg);
      });
    });
  }

  acknowledge() {
    this.processed++;
  }
}

// --- Demo ---
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

(async () => {
  const mq = new MessageQueue("tasks");
  const results = [];

  async function consumer(name, stopSignal) {
    while (!stopSignal.stopped) {
      const msg = await mq.dequeue(200);
      if (!msg) continue;
      await sleep(50);
      mq.acknowledge();
      results.push({ worker: name, id: msg.id, body: msg.body });
      console.log(`  [${name}] Processed msg#${msg.id}: ${msg.body}`);
    }
  }

  const signal = { stopped: false };
  const c1 = consumer("Consumer-1", signal);
  const c2 = consumer("Consumer-2", signal);

  console.log("=== Producing messages ===");
  for (let i = 1; i <= 6; i++) {
    const msg = mq.enqueue(`task-${i}`);
    console.log(`  [Producer] Enqueued msg#${msg.id}: ${msg.body}`);
    await sleep(20);
  }

  await sleep(800);
  signal.stopped = true;
  await Promise.all([c1, c2]);

  console.log(`\nEnqueued: ${mq.enqueued}, Processed: ${mq.processed}`);
})();
```

## Related Patterns

- **Publish-Subscribe** — publish-subscribe delivers one message to many subscribers; message queue delivers each message to exactly one consumer.
- **Producer-Consumer** — a message queue is the durable, distributed implementation of the in-process producer-consumer pattern.
- **Dead Letter Queue** — messages that cannot be processed after N retries are routed to a dead letter queue for inspection.
- **Retry** — pair message queue consumers with retry logic and backoff to handle transient processing failures.
