Design a Multi-Channel Notification Service With Retries
In a 45-minute LLD interview, the interviewer looks for behavioral composition without class explosion (combining retries and rate limits on arbitrary channels using the Decorator pattern) and decoupled pub-sub event dispatching (routing business events to interested channel observers without hardcoding dispatch calls in domain services). Scope the solution to ChannelSender strategy interface, concrete channel senders (Email, SMS, Push), ChannelSenderDecorator hierarchy (RetryDecorator, RateLimitDecorator), and topic-based NotificationService event bus. Mention async task queues (std::jthread workers) and user channel preference filtering as natural extensions.
Problem statement & clarifying requirements
Design a Multi-Channel Notification Service capable of dispatching notifications across diverse delivery channels (Email, SMS, Push). The system must allow channels to be dynamically wrapped with cross-cutting resilience behaviorsβsuch as automatic retries on failure and rate-limiting per windowβwithout modifying the underlying channel senders. Events must be routed to subscribers based on topic subscriptions using the Observer pattern.
Each delivery medium implements a common ChannelSender interface (Strategy pattern), declaring send(msg) and channelName().
Using the Decorator pattern, where each decorator (RetryDecorator, RateLimitDecorator) implements ChannelSender and wraps an inner sender, allowing arbitrary stacking.
A centralized NotificationService acts as an event bus (Observer pattern), maintaining topic-to-channel subscriber mappings.
It logs the terminal failure, returns false, and lets other subscribed channels continue their dispatch.
First, sketch the design yourself
Spend 5 minutes listing the classes, who owns whom, and which interface hides the part that changes.
- 1.
Avoid an inheritance explosion for channel add-ons: Creating
RetryingEmailSender,RateLimitedSmsSender, andRateLimitedRetryingEmailSendercreates 2^N subclasses. Use the Decorator pattern to compose behaviors dynamically. - 2.
Decouple event publishers from delivery channels: High-level services (e.g. OrderService, SecurityService) should not know about SMTP servers or SMS gateways. They publish named topic events (
ORDER_CONFIRMATION,SECURITY_ALERT), and the Observer pattern handles the routing. - 3.
Preserve uniform interfaces: By making decorators implement
ChannelSender, callers treat a rawEmailSenderand a doubly-wrappedRetryDecorator(RateLimitDecorator(EmailSender))identically.
How to think through this problem (5-step walkthrough)
In a 45-minute LLD interview, the interviewer looks for behavioral composition without class explosion (combining retries and rate limits on arbitrary channels using the Decorator pattern) and decoupled pub-sub event dispatching (routing business events to interested channel observers without hardcoding dispatch calls in domain services). Scope the solution to ChannelSender strategy interface, concrete channel senders (Email, SMS, Push), ChannelSenderDecorator hierarchy (RetryDecorator, RateLimitDecorator), and topic-based NotificationService event bus. Mention async task queues (std::jthread workers) and user channel preference filtering as natural extensions.
Not every noun in the prompt deserves its own class. Separate dumb data structs from stateful managers, and turn trivial nouns into fields or enums.
NotificationMessageValue struct bundling recipient identifier, subject/title, and message body.
ChannelSenderCore Strategy interface declaring send(msg) and channelName(). Defines the common contract for both raw senders and decorators.
EmailSender / SmsSenderConcrete channel strategy classes handling format validation and protocol-specific dispatch.
ChannelSenderDecoratorAbstract Decorator base class implementing ChannelSender and owning a std::shared_ptr<ChannelSender> wrapped_ to forward calls.
RetryDecorator / RateLimitDecoratorConcrete decorators wrapping any sender with automatic retransmission loops or per-window rate-limiting quotas.
NotificationServiceObserver event router maintaining topic subscription maps (unordered_map<string, vector<shared_ptr<ChannelSender>>>) and fanning out messages.
RetryCount / TopicNameTrap noun β beginners often create separate RetryCount, MaxQuota, or TopicName classes. A topic is a simple std::string key, and retry/rate limits are primitive integer configurations inside the decorator instances.
A beginner uses class inheritance to add features: RetryingEmailSender, RateLimitedEmailSender, RateLimitedRetryingEmailSender, etc. With 3 channels and 3 cross-cutting behaviors (retry, rate limit, logging), subclassing requires $3 Γ 2^3 = 24$ classes, causing subclass explosion. Furthermore, hardcoding emailSender.send() directly inside business services (e.g. OrderService) tightly couples business events to concrete delivery channels.
Hardcoding SMTP or SMS API calls directly inside business services couples domain logic to third-party telecommunication APIs.
Subclassing each channel to add retry or rate-limiting behaviors results in an exponential combinatorial explosion of classes.
Directly invoking individual channel instances on every business event forces editing domain services whenever a new channel or recipient attaches.
- β
Transparent decorator interchangeability: Every decorator implements
ChannelSenderand forwards unhandled calls towrapped_, ensuring the outer wrapper can be used anywhere a baseChannelSenderis expected. - β
Shared channel subscription invariant: Because the same decorated sender instance can be subscribed to multiple topics (e.g.
SECURITY_ALERTandORDER_CONFIRMATION), channels are managed usingstd::shared_ptr<ChannelSender>. - β
Terminal failure isolation: If a decorated channel exhausts all retry attempts or exceeds its rate-limiting window quota, it returns
falsewithout throwing, allowing sibling channel subscribers to proceed with delivery.
Entities & design patterns used
Unlock Full UML Diagram & C++23 Solution
The complete UML diagram, production-ready C++23 code, follow-up extensions, and design pitfall analysis are part of Brute2Best Pro. All 7 foundation chapters are 100% free.
First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.