Logic Behind The Code

Notification system LLD

A visual walkthrough of direct notifications and broadcast messaging using Strategy Pattern for channels and Observer Pattern for subscribers.

3Notification channels
2Core patterns
BroadcastObserver fan-out

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

01

Single Responsibility

Channel classes only send messages, subject manages subscribers, observer maps preferences to channels, and service coordinates direct send.

02

Open/Closed

To add Push Notification, create another strategy and register it in the strategy map.

03

Dependency Inversion

MessageService depends on NotificationStrategy, not concrete channel classes.

04

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();
Interview answer
This is Strategy Pattern: the sending algorithm is swappable at runtime.

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");
Runtime path
SubjectObserverUser PreferencesStrategy MapChannels

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);
}
Pattern
This is Strategy Pattern. Email, SMS, WhatsApp are separate algorithms behind one contract.

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);
    }
}
Design principle
This follows Dependency Inversion because the service talks to an interface.

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();
}
Pattern
This is Observer Pattern. A state change triggers updates to all subscribers.

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());
    }
}
Interview answer
User-specific fan-out is outside the subject, so the subject stays loosely coupled.

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

Client creates message
Choose notification channel
MessageService delegates to strategy
Channel sends direct notification
For broadcast, Subject notifies observers
Subscribers receive update

UML Diagram: IS-A and HAS-A

NotificationStrategy
Email
SMS
MessageSubject
MessageConcreteSubject
List<MessageObserver>
MessageObserver
MessageConcreteObserver
User
MessageService
NotificationStrategy

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.