Design Intent
What your code is doing
Strategy Pattern
NotificationStrategy gives Email, SMS, and WhatsApp the same send contract. New channels can be added without changing the caller.
Observer Pattern
MessageSubject broadcasts a new message to all registered observers. Each observer then sends through the user preferred channels.
Thread-Friendly Collections
CopyOnWriteArrayList keeps observer iteration safe while observers are added or removed.
Design Principles
How SOLID appears in this design
Single Responsibility
Channel classes only send messages, subject manages subscribers, observer maps preferences to channels, and service coordinates direct send.
Open/Closed
To add Push Notification, create another strategy and register it in the strategy map.
Dependency Inversion
MessageService depends on NotificationStrategy, not concrete channel classes.
Broadcast Fan-Out
The subject does not know how each user receives notifications. Observers own user-specific delivery preferences.
Request Flow
How direct and broadcast notifications move
Direct Send
MessageService sends through one selected strategy
The client chooses the concrete channel and passes it as a strategy. The service calls the interface method.
MessageService m1 = new MessageService("ID01", "Hello", new Email());
m1.sendNotification();Broadcast Send
Subject notifies all registered observers
When the subject message changes, every observer receives the update and sends through that user preferred channels.
subject.add(ob1);
subject.add(ob2);
subject.setMessage("Broadcast: System Maintenance at 10PM");Code Walkthrough
Explain each important snippet like an interview answer
Channel Contract
One interface for every notification type
Every channel implements the same send method.
interface NotificationStrategy {
boolean send(String message, String id);
}Direct Service
MessageService delegates sending
The service stores message, user id, and selected strategy.
class MessageService {
private NotificationStrategy strategy;
public void sendNotification() {
strategy.send(Message, UserId);
}
}Subject
Broadcast message source
The subject keeps observers and calls update when a new message is published.
public void setMessage(String message) {
this.message = message;
Notify();
}Observer
User preferences decide delivery channels
The observer reads user preferences and uses the strategy map.
for (NotificationTypes type : user.getPreferredNotificationTypes()) {
NotificationStrategy strategy = strategyMap.get(type);
if (strategy != null) {
strategy.send(msg, user.getId());
}
}Run The Code
Read it here, run it in the embedded Java compiler
Java Source
Compiler-friendly version: no package line and runnable class is Main. It runs direct-send plus broadcast automatically.
Embedded Compiler
Copy the Java source on the left and paste it into the compiler. It runs immediately with direct and broadcast examples.
Interview Ready
Notes For Interview
How to explain the design
This Notification System LLD separates notification channel selection from broadcast subscription. Direct sending uses `NotificationStrategy`, so Email, SMS, and WhatsApp are interchangeable channels. Broadcast messaging uses Observer Pattern, where a subject keeps subscribers and notifies each observer when a message is published.
Flow Diagram
UML Diagram: IS-A and HAS-A
Core classes
- NotificationStrategy: contract for sending a message through a channel.
- Email, SMS, WhatsApp: concrete channel strategies.
- MessageService: stores message data and delegates sending to a selected strategy.
- MessageSubject: manages subscriber registration and broadcast notification.
- MessageObserver: receives broadcast updates.
Why these patterns are used
Strategy Pattern is used because the way a notification is delivered changes by channel. Email, SMS, WhatsApp, push notification, and in-app notification all have different providers, payloads, failure behavior, and latency. The service should not contain channel-specific `if-else` logic. By depending on `NotificationStrategy`, the sender can plug in a channel at runtime.
Observer Pattern is used for broadcast use cases. A subject represents the event source, and observers represent subscribers. When a new broadcast message is published, the subject does not need to know each subscriber's internal behavior. It simply calls `update()` on all registered observers. This keeps the publisher loosely coupled from receivers.
Algorithm explanation
Direct notification flow is straightforward: create message data, choose a notification strategy, and call `send`. The selected strategy owns provider-specific delivery behavior. This is a runtime polymorphism example: the same method call behaves differently depending on the concrete strategy object.
Broadcast flow is a fan-out algorithm. The subject stores a list of observers. When a message arrives, it iterates through subscribers and invokes their `update` method. In production, this fan-out should usually be asynchronous through a queue, because one slow or failed subscriber should not block the whole broadcast.
Strong interview answer
"I would use Strategy Pattern for notification channels and Observer Pattern for broadcast. Strategy lets the client choose Email, SMS, or WhatsApp without changing the service. Observer lets many users subscribe to a subject and receive updates when a broadcast event happens."
Production improvements
- Add retry policy, dead-letter queue, and failure tracking.
- Make sends asynchronous through a queue such as Kafka, SQS, or RabbitMQ.
- Add user preferences, quiet hours, and unsubscribe rules.
- Add templates, localization, rate limits, and provider fallback.
- Track notification delivery states: queued, sent, failed, delivered, read.
Weakness to mention
The in-memory demo shows patterns well, but a production notification system must handle asynchronous delivery, provider failures, retries, idempotency, and user preferences.