> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notification System Bottlenecks and the Real World

> Explains notification system bottlenecks and production solutions using durable queues, idempotency, worker scaling, retries, dead-letter queues, central gateways, and priority routing.

Now we reach the final step of our framework: bottlenecks and scaling. This article explains where a notification design commonly breaks as the number of notifications grows and how production systems address those limits.

## The queue is the system's heart

A durable queue is central to scalable notification systems. In interviews, call out concrete queue implementations such as [Amazon SQS](https://aws.amazon.com/sqs/) — a strong fit because it provides acknowledgement semantics and built-in dead-letter queues. [RabbitMQ](https://www.rabbitmq.com/) is another common option for systems that need more advanced routing.

If an event causes a notification flood, queue lengths will grow and may not decrease until you increase processing capacity. A rising queue size is a clear signal to add more workers or increase throughput. Note that this design accepts duplicate tickets (see idempotency below).

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Bottlenecks-and-the-Real-World/notification-system-queue-workers-redis-diagram.jpg?fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=8e54a7f1bdc17fed7387c156caafdf6d" alt="A dark-themed architecture diagram of a notification system showing a database and notification server pushing jobs into a queue. Multiple worker gears pull from the queue (with Redis and an external provider shown) to process background jobs and deliver notifications." width="1920" height="1080" data-path="images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Bottlenecks-and-the-Real-World/notification-system-queue-workers-redis-diagram.jpg" />
</Frame>

## Delivery semantics: at-least-once and idempotency

Queues often deliver the same message multiple times. This design intentionally uses at-least-once delivery: when the system is unsure whether a notification was delivered, it retries and may send the notification again. Sending a duplicate is preferable to losing a notification.

To prevent duplicates from reaching users, perform an idempotency check just before calling the external provider. Use a fast store (e.g., [Redis](https://redis.io/)) to record a notification’s unique idempotency key so the provider is invoked only once per logical notification. With that guard, end users rarely see duplicates.

<Callout icon="lightbulb" color="#1CB2FE">
  At-least-once delivery is a practical choice for distributed systems: you can’t reliably guarantee exactly-once across distributed components, but you can make redelivery harmless using idempotency and deduplication.
</Callout>

## Concise interview-ready notification flow

Use this short, structured answer when asked about notification delivery in an interview:

1. The notify API writes a notification record with a unique notification ID to the database and immediately returns Accepted.
2. A background job enqueues a ticket so the notification can be delivered asynchronously.
3. We keep one queue per channel (email, push, SMS, etc.) so a slow provider on one channel cannot block others.
4. Workers pick tickets from the queues and consult `Redis` (or a similar fast store) to check whether the notification has already been sent.
5. If it has not been sent, the worker evaluates the user's preferences and proceeds only if delivery is allowed.
6. The worker calls the external provider to send the notification.
7. On failures we retry with exponential backoff; messages that still fail are routed to a dead-letter queue.

## Components, responsibilities, and scaling signals

| Component | Responsibility | Scaling / Health Signals |
| - | - | - |
| Queue (SQS / RabbitMQ) | Durable ingestion and buffering | Queue length, enqueue rate, time-in-queue |
| Workers | Process tickets, check idempotency, call providers | Worker CPU/RAM, processing latency, success rate |
| Redis (fast store) | Idempotency checks and transient state | Cache hit rate, request latency |
| External providers | Email/SMS/push delivery | Provider error rate, API latency |
| Dead-letter queue | Capture permanently failing messages | DLQ growth, failure reasons |

Use these metrics to decide when to scale horizontally (more workers) or vertically (bigger instances).

## Alternatives: event streaming and centralized gateways

You can implement durable ingestion and buffering with event streaming platforms (e.g., Kafka) or classic queues. The conceptual pattern stays the same: durable ingestion → buffered processing → retries → dead-letter handling.

Real companies extend this basic pattern:

* LinkedIn initially had each team send notifications independently, producing duplicate emails. They centralized delivery in a gateway called Air Traffic Controller, which routes events from Kafka and centralizes delivery logic. This reduced emails by \~50% and complaints by more than 65%.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/CjAdnUFCNZoN2k5-/images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Bottlenecks-and-the-Real-World/linkedin-ads-jobs-news-kafka-gateway.jpg?fit=max&auto=format&n=CjAdnUFCNZoN2k5-&q=85&s=4275867418da4c0fba4a417cf4597312" alt="A dark-themed diagram titled &#x22;What companies actually run&#x22; showing Ads, Jobs, and News teams feeding into Kafka, which routes to an &#x22;Air Traffic Controller&#x22; single gateway and then to one member. The LinkedIn logo appears above the central flow." width="1920" height="1080" data-path="images/System-Design-for-Interview-Prep/Fan-Out-One-Event-Many-Readers/Notification-System-Bottlenecks-and-the-Real-World/linkedin-ads-jobs-news-kafka-gateway.jpg" />
</Frame>

* Uber uses per-priority buckets for push notifications so urgent messages (e.g., trip updates) get higher priority than promotional offers. Priority queues ensure delivery order matches business urgency.

## Best practices for production-ready notification systems

* Use durable ingestion (SQS / RabbitMQ / Kafka) to absorb bursts.
* Buffer and horizontally scale workers based on queue depth and latency.
* Enforce idempotency at the provider boundary (use `notification_id` or an idempotency key stored in `Redis`).
* Respect user preferences and apply delivery rules before provider calls.
* Implement exponential backoff and a dead-letter queue for permanent failures.
* Centralize routing and deduplication when multiple teams produce notifications (reduces user noise).
* Add priority buckets if different messages have different urgency.

## Links and references

* [Amazon SQS](https://aws.amazon.com/sqs/)
* [RabbitMQ](https://www.rabbitmq.com/)
* [Redis](https://redis.io/)
* [Event Streaming with Kafka](https://learn.kodekloud.com/user/courses/event-streaming-with-kafka)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/system-design-for-interview-prep/module/36e67187-d5c7-44a8-9c92-65e2548b2f80/lesson/3f6e7c8f-447e-490b-b2d5-314415824057" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.