2Brute2Best
πŸ”₯0
P10

Design a Multi-Channel Notification Service With Retries

Medium High-frequency interview topic Pro OnlyObserverStrategyDecorator
⚑ 30-Second Interview Cheat SheetP10 · 45-Min Design Plan
3 Patterns Combined
1. Scope Boundaries

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.

2. Core Noun Entities
NotificationMessagedata-struct
ChannelSenderinterface
EmailSender / SmsSenderstateful-class
ChannelSenderDecoratorinterface
3. Hotspot β†’ Pattern
Channel delivery medium (Email vs SMS vs Push) β†’ Strategy (ChannelSender interface)
Dynamic composition of resilience behaviors (Retries, Rate Limiting, Metrics) β†’ Decorator (ChannelSenderDecorator with RetryDecorator and RateLimitDecorator)
Routing domain events to multiple delivery channels β†’ Observer (NotificationService topic subscription router)
01

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.

Questions you should ask the interviewer first
β€œHow are different delivery mediums (Email, SMS, Push) abstracted?”

Each delivery medium implements a common ChannelSender interface (Strategy pattern), declaring send(msg) and channelName().

β€œHow can resilience features like retries and rate limiting be combined on a single channel?”

Using the Decorator pattern, where each decorator (RetryDecorator, RateLimitDecorator) implements ChannelSender and wraps an inner sender, allowing arbitrary stacking.

β€œHow are system events routed to their intended channels?”

A centralized NotificationService acts as an event bus (Observer pattern), maintaining topic-to-channel subscriber mappings.

β€œWhat happens if a decorated sender exhausts its retry attempts?”

It logs the terminal failure, returns false, and lets other subscribed channels continue their dispatch.

02

First, sketch the design yourself

Spend 5 minutes listing the classes, who owns whom, and which interface hides the part that changes.

  1. 1.

    Avoid an inheritance explosion for channel add-ons: Creating RetryingEmailSender, RateLimitedSmsSender, and RateLimitedRetryingEmailSender creates 2^N subclasses. Use the Decorator pattern to compose behaviors dynamically.

  2. 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. 3.

    Preserve uniform interfaces: By making decorators implement ChannelSender, callers treat a raw EmailSender and a doubly-wrapped RetryDecorator(RateLimitDecorator(EmailSender)) identically.

03

How to think through this problem (5-step walkthrough)

Step 1 Β· Scope the 45-minute target & core boundary

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.

Step 2 Β· Extract candidate nouns (and reject false classes)

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.

NotificationMessage
Value Struct

Value struct bundling recipient identifier, subject/title, and message body.

ChannelSender
Abstract Interface

Core Strategy interface declaring send(msg) and channelName(). Defines the common contract for both raw senders and decorators.

EmailSender / SmsSender
Stateful Class / Manager

Concrete channel strategy classes handling format validation and protocol-specific dispatch.

ChannelSenderDecorator
Abstract Interface

Abstract Decorator base class implementing ChannelSender and owning a std::shared_ptr<ChannelSender> wrapped_ to forward calls.

RetryDecorator / RateLimitDecorator
Stateful Class / Manager

Concrete decorators wrapping any sender with automatic retransmission loops or per-window rate-limiting quotas.

NotificationService
Stateful Class / Manager

Observer event router maintaining topic subscription maps (unordered_map<string, vector<shared_ptr<ChannelSender>>>) and fanning out messages.

RetryCount / TopicName
Reject as Class β†’ Field / Method

Trap 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.

Step 3 Β· Draft the naive design & spot what breaks

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.

Step 4 Β· Map change hotspots to design patterns
Channel delivery medium (Email vs SMS vs Push)β†’ Strategy (ChannelSender interface)

Hardcoding SMTP or SMS API calls directly inside business services couples domain logic to third-party telecommunication APIs.

Dynamic composition of resilience behaviors (Retries, Rate Limiting, Metrics)β†’ Decorator (ChannelSenderDecorator with RetryDecorator and RateLimitDecorator)

Subclassing each channel to add retry or rate-limiting behaviors results in an exponential combinatorial explosion of classes.

Routing domain events to multiple delivery channels→ Observer (NotificationService topic subscription router)

Directly invoking individual channel instances on every business event forces editing domain services whenever a new channel or recipient attaches.

Step 5 Β· Lock down C++23 ownership, invariants & concurrency
  • βœ“

    Transparent decorator interchangeability: Every decorator implements ChannelSender and forwards unhandled calls to wrapped_, ensuring the outer wrapper can be used anywhere a base ChannelSender is expected.

  • βœ“

    Shared channel subscription invariant: Because the same decorated sender instance can be subscribed to multiple topics (e.g. SECURITY_ALERT and ORDER_CONFIRMATION), channels are managed using std::shared_ptr<ChannelSender>.

  • βœ“

    Terminal failure isolation: If a decorated channel exhausts all retry attempts or exceeds its rate-limiting window quota, it returns false without throwing, allowing sibling channel subscribers to proceed with delivery.

04

Entities & design patterns used

Brute2Best Pro Content

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.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers