LLD: Interview Question on Notification System
If you’re looking for paid 1:1 mentorship with a strong focus on LLD (core emphasis on Multithreading), HLD, DSA, and system design research papers, feel free to reach out.
📩 Contact: programmingappliedai@gmail.com
Question
Design a Notification Service where:
The system supports multiple clients (Amazon, AWS, etc.)
Each client has multiple subscribers
There are multiple severity levels:
HIGH,MEDIUM,LOWEach subscriber can define a custom notification strategy per client per severity
The system must be:
Extensible (new severity levels)
Extensible (new notification channels like Pager, Slack, Insta, etc.)
Scalable
Configurable
🏗 High-Level Architecture
Client Service → Notification API → Notification Processor → Channel Dispatchers
↓
Database
Components:
Notification API
Accepts notification requests
Validates client + severity
Notification Processor
Fetches subscriber strategies
Resolves delivery channels
Pushes to channel-specific queues
Channel Dispatchers
SMS Service
Email Service
Phone Service
Future: Pager, Slack, Insta, etc.
Database
Stores subscriber configuration
Stores client definitions
Stores channel mappings
🗄 Database Design
Use PostgreSQL (or any relational DB) because:
Structured relationships
Easy querying
ACID guarantees for configuration
Flexible with extensibility
📊 Schema Design
1. Clients Table
CREATE TABLE clients (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL
);
2. Subscribers Table
CREATE TABLE subscribers (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
phone VARCHAR(20),
email VARCHAR(255)
);
3. Severity Levels (Extensible)
CREATE TABLE severity_levels (
id BIGSERIAL PRIMARY KEY,
code VARCHAR(10) UNIQUE NOT NULL -- H, M, L
);
This allows adding new levels like CRITICAL, INFO without schema change.
4. Notification Channels (Extensible)
CREATE TABLE notification_channels (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL -- PHONE, EMAIL, MSG, PAGER
);
Adding new channels = just insert row.
5. Subscription Strategy Mapping (Core Table)
CREATE TABLE subscriber_strategies (
id BIGSERIAL PRIMARY KEY,
subscriber_id BIGINT REFERENCES subscribers(id),
client_id BIGINT REFERENCES clients(id),
severity_id BIGINT REFERENCES severity_levels(id),
channel_id BIGINT REFERENCES notification_channels(id)
);
This is a many-to-many mapping.
For example:
Subscriber 2 + Amazon + HIGH → PHONE
Subscriber 2 + Amazon + HIGH → EMAIL
Subscriber 2 + Amazon + HIGH → MSG
🔍 Example Data Representation
For:
Subscriber 2
Amazon:
H(phone,msg,email)
M(msg,email)
L(email)
We insert multiple rows in subscriber_strategies.
This makes system fully extensible.
🚀 Notification Flow
Step 1: Client sends notification
POST /notify
{
"clientName": "amazon",
"severity": "HIGH",
"message": "Order failed"
}
Step 2: Processor Logic
Fetch client_id
Fetch severity_id
Query strategies:
SELECT s.*, c.name as channel
FROM subscriber_strategies ss
JOIN subscribers s ON s.id = ss.subscriber_id
JOIN notification_channels c ON c.id = ss.channel_id
WHERE ss.client_id = ?
AND ss.severity_id = ?
Group by subscriber
Dispatch notifications per channel
💻 Sample Code (Java – Strategy Retrieval)
class NotificationService {
private StrategyRepository strategyRepository;
private ChannelDispatcherFactory dispatcherFactory;
public void sendNotification(String clientName, String severityCode, String message) {
List<NotificationTarget> targets =
strategyRepository.fetchStrategies(clientName, severityCode);
for (NotificationTarget target : targets) {
NotificationChannelDispatcher dispatcher =
dispatcherFactory.getDispatcher(target.getChannel());
dispatcher.send(target.getSubscriber(), message);
}
}
}
Channel Dispatcher Interface (Extensible)
interface NotificationChannelDispatcher {
void send(Subscriber subscriber, String message);
}
Email Implementation
class EmailDispatcher implements NotificationChannelDispatcher {
public void send(Subscriber subscriber, String message) {
System.out.println("Sending Email to " + subscriber.getEmail());
}
}
Phone Implementation
class PhoneDispatcher implements NotificationChannelDispatcher {
public void send(Subscriber subscriber, String message) {
System.out.println("Calling " + subscriber.getPhone());
}
}
Factory Pattern (For Extensibility)
class ChannelDispatcherFactory {
public NotificationChannelDispatcher getDispatcher(String channel) {
switch (channel) {
case "EMAIL":
return new EmailDispatcher();
case "PHONE":
return new PhoneDispatcher();
case "MSG":
return new SmsDispatcher();
default:
throw new IllegalArgumentException("Unsupported channel");
}
}
}
To add Slack:
Add row in
notification_channelsAdd
SlackDispatcherUpdate factory (or use reflection for better extensibility)
📈 Scaling Considerations
1️⃣ Use Kafka / Queue
Instead of direct dispatch:
Notification API → Kafka → Worker Consumers → Channel Services
Benefits:
Retry support
Dead letter queue
Rate limiting per channel
2️⃣ Indexing
Add indexes:
CREATE INDEX idx_strategy_lookup
ON subscriber_strategies(client_id, severity_id);
3️⃣ Caching
Cache strategy lookup in:
Redis
In-memory cache (if low churn)
🔥 Extensibility Discussion (Important for Interview)
Adding New Severity Level
Insert into
severity_levelsNo schema change
No code change
Adding New Channel
Insert into
notification_channelsAdd new Dispatcher class
No DB schema change
Adding Channel Preferences (like priority order)
Add column:
priority INT
🧠 Design Patterns Used
Strategy Pattern
Factory Pattern
Open-Closed Principle
Separation of Concerns
🏁 Final Architecture Summary
| Concern | Solution |
| --------------------- | ------------------ |
| Multi-client | client table |
| Per-subscriber config | mapping table |
| Extensibility | normalized tables |
| Scaling | queue-based async |
| Retry | DLQ |
| Performance | indexing + caching |
⭐ Why This Is a Strong Interview Answer
Proper DB normalization
Extensible design
Clean code separation
Production scalability thinking
Covers schema + code + infra

