Bird
Raised Fist0

Notification System OOP - Factory Pattern, Channel Abstraction, Template

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
🏗️
Notification System OOP - Factory Pattern, Channel Abstraction, Template
Design a flexible notification system using Factory and Template patterns with channel abstraction.
mediumInfrastructure and Utility SystemsFreq: medium
Prerequisites:OOP BasicsPolymorphism
⚙️
Functional Requirements
  • Support multiple notification channels (Email, SMS, Push)
  • Use Factory pattern to instantiate notification channels
  • Use Template pattern to format messages before sending
  • Allow adding new channels without modifying existing classes (Open-Closed Principle)
  • Ensure thread-safe sending of notifications
  • Provide retry mechanism for failed notifications
  • Log notification delivery status
👤
Use Cases
  • Send notification via Email channel
  • Send notification via SMS channel
  • Send notification via Push Notification channel
  • Add new notification channel without modifying existing code
  • Format notification message using templates
  • Handle concurrent notification requests
  • Retry failed notifications
🧑‍💻
Actors
  • User
  • NotificationService
  • NotificationChannel
  • TemplateEngine
❓
Clarifying Questions to Ask
  • How many concurrent threads are expected to send notifications simultaneously?
  • Is there a pricing strategy or rate limiting per channel?
  • Should the system support bulk notifications or only single recipient per notification?
  • What is the retry policy for failed notifications?
  • Are templates stored externally or hardcoded?
  • Is message personalization required per user?
  • What happens if a notification channel is temporarily unavailable?
Out of Scope
✗ User authentication and authorization
✗ Notification scheduling and queuing
✗ Third-party integration details for sending messages
✗ UI or frontend components
⚡
QPS Calculation
expected qps500
peak qps1000
average message size bytes1024
daily notifications43200000
daily data gb41
🖥️
Server Estimate
notification service instances5
channel service instances[object Object]
Estimates based on expected concurrent users and notification volume to ensure scalability and fault tolerance.
🔢
Enumerations
enum NotificationType
EMAIL | SMS | PUSH
Defines supported notification channels explicitly to prevent typo bugs and enable exhaustive switch-case handling.
enum NotificationStatus
PENDING | SENT | FAILED | RETRYING
Tracks lifecycle state of a notification to manage retries and logging accurately.
📦
Notification
4 fields
private final String recipientprivate final NotificationType typeprivate final String messageTemplateprivate volatile NotificationStatus status
Represents a notification request with recipient, type, message template, and status. Holds immutable data except status which is volatile for thread visibility.
public String getRecipient()Returns the recipient identifier.
public NotificationType getType()Returns the notification channel type.
public String getMessageTemplate()Returns the message template string.
public NotificationStatus getStatus()Returns the current notification status.
public synchronized void setStatus(NotificationStatus status)Atomically updates the notification status.
📦
NotificationChannel
1 fields
protected final String channelName
Abstract base class for all notification channels. Defines template method for sending notifications with common steps.
public final synchronized boolean sendNotification(Notification notification)Template method that formats message and sends notification atomically to prevent concurrent send conflicts.
protected abstract String formatMessage(String template, Notification notification)Abstract method to format message based on template and notification data.
protected abstract boolean send(String formattedMessage, String recipient)Abstract method to send the formatted message to recipient.
📦
EmailChannel
1 fields
private final String smtpServer
Concrete implementation of NotificationChannel for sending emails.
protected String formatMessage(String template, Notification notification)Formats email message using template.
protected boolean send(String formattedMessage, String recipient)Sends email via SMTP server.
📦
SmsChannel
1 fields
private final String smsGateway
Concrete implementation of NotificationChannel for sending SMS messages.
protected String formatMessage(String template, Notification notification)Formats SMS message using template.
protected boolean send(String formattedMessage, String recipient)Sends SMS via SMS gateway.
📦
PushChannel
1 fields
private final String pushServiceEndpoint
Concrete implementation of NotificationChannel for sending push notifications.
protected String formatMessage(String template, Notification notification)Formats push notification message using template.
protected boolean send(String formattedMessage, String recipient)Sends push notification via push service.
📦
NotificationChannelFactory
0 fields
Factory class responsible for creating NotificationChannel instances based on NotificationType. Encapsulates channel creation logic.
public static NotificationChannel createChannel(NotificationType type)Returns a concrete NotificationChannel instance for the given NotificationType.
📦
NotificationService
1 fields
private final ExecutorService executorService
Facade service that accepts Notification requests, uses factory to get channel, and sends notifications asynchronously with retry logic.
public void sendNotification(Notification notification)Submits notification sending task to executor service for asynchronous processing.
private void retryNotification(Notification notification, int retryCount)Retries sending notification with exponential backoff.
🔗
Relationships
NotificationService──►NotificationChannelFactory[1..1]
associationNotificationChannelFactory exists independently and is used by NotificationService.
NotificationService──►NotificationChannel[1..*]
associationNotificationService uses NotificationChannel instances to send notifications.
NotificationChannel──►Notification[1..1]
associationNotificationChannel processes Notification objects.
NotificationChannelFactory──►NotificationChannel[1..1]
associationFactory creates NotificationChannel instances.
🗄️
notifications
PK: recipient
notification_id UUIDrecipient Stringtype NotificationTypemessage_template Stringstatus NotificationStatustimestamp Timestamp
Stores notification requests and their statuses for tracking and retry.
🗄️
notification_logs
PK: notification_id
log_id UUIDnotification_id UUIDevent_time Timestampstatus NotificationStatusmessage String
Logs delivery events and status changes for audit and debugging.
🔌
POST /notifications/send
Request
{
  "recipient": "String",
  "type": "NotificationType",
  "messageTemplate": "String"
}
Response
{
  "notificationId": "UUID",
  "status": "PENDING"
}
Submits a notification request for asynchronous sending.
🔌
GET /notifications/status
Request
{
  "notificationId": "UUID"
}
Response
{
  "notificationId": "UUID",
  "status": "NotificationStatus"
}
Retrieves current status of a notification.
🔌
POST /notifications/retry
Request
{
  "notificationId": "UUID"
}
Response
{
  "notificationId": "UUID",
  "status": "RETRYING"
}
Triggers manual retry of a failed notification.

Practice

(1/5)
1. In the Food Ordering System, should the MenuItem be designed as an abstract class or an interface, considering it has both state (price, description) and behavior (calculateDiscount)? Why?
easy
A. Abstract class, because it can hold shared state like price and provide default behavior implementations.
B. Interface, because it allows multiple inheritance and enforces behavior without state.
C. Interface, because state can be stored externally and behavior defined in default methods.
D. Abstract class, because interfaces cannot define any methods with implementation.

Solution

  1. Step 1: Analyze state and behavior requirements

    MenuItem holds state (price, description) and behavior (calculateDiscount) that should be shared or overridden.
  2. Step 2: Abstract class vs interface capabilities

    Abstract classes can hold state and provide default method implementations; interfaces cannot hold instance state (before Java 8+ default methods, but still no fields).
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    State + behavior with shared code -> Abstract class [check]
Hint: State + behavior together -> abstract class, not interface [check]
Common Mistakes:
2. A class provides a createIterator() method that returns an object allowing traversal over its elements without exposing its internal data structure. Which design pattern is being applied and why is this the correct choice?
easy
A. Factory Method pattern, because it encapsulates object creation logic.
B. Decorator pattern, because it adds traversal behavior dynamically.
C. Iterator pattern, because it separates traversal logic from the collection's internal structure.
D. Composite pattern, because it treats individual and composite objects uniformly.

Solution

  1. Step 1: Identify the role of createIterator()

    This method returns an object that allows clients to traverse elements without exposing the collection's internal representation, which is the core intent of the Iterator pattern.
  2. Step 2: Eliminate other patterns

    Factory Method focuses on object creation, Decorator adds responsibilities dynamically, and Composite manages tree structures. None focus on traversal abstraction.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Iterator separates traversal from collection internals [check]
Hint: Iterator hides structure, exposes traversal [check]
Common Mistakes:
3. You are designing a base class Vehicle that provides a template method for starting the engine and driving, but also maintains internal state like fuel level. Should Vehicle be an abstract class or an interface, and why?
easy
A. Abstract class, because it can hold shared state and provide default method implementations.
B. Interface, because it allows multiple inheritance and enforces method signatures without state.
C. Interface, because Java 8+ interfaces can have default methods and fields for state.
D. Abstract class, because interfaces cannot define any behavior or state.

Solution

  1. Step 1: Consider state management

    Since Vehicle maintains internal state like fuel level, it requires fields, which interfaces cannot hold.
  2. Step 2: Evaluate method implementations

    Abstract classes can provide default implementations for template methods, supporting the Template Method pattern.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Abstract class supports state and default behavior [check]
Hint: State needs abstract class, not interface [check]
Common Mistakes:
4. In a concurrent Job Scheduler, if the scheduleJob() method is not synchronized, which exact thread interleaving causes the same Job to be scheduled twice?
medium
A. Thread 1 and Thread 2 both call scheduleJob() simultaneously and both succeed without checks.
B. Thread 1 adds job, then Thread 2 removes it before completion, causing inconsistency.
C. Thread 1 checks if job exists -> false, context switch; Thread 2 checks -> false; both add job concurrently.
D. Thread 1 waits for Thread 2 to finish scheduling, causing deadlock.

Solution

  1. Step 1: Identify the check-then-act race condition

    Both threads check if the job is already scheduled before adding. Without synchronization, both see job absent.
  2. Step 2: Trace thread interleaving

    Thread 1 checks -> false, context switch before adding; Thread 2 checks -> false, adds job; Thread 1 resumes and adds job again, causing double scheduling.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Check-then-act gap without synchronization -> double scheduling [check]
Hint: Check-then-act gap -> double scheduling race [check]
Common Mistakes:
5. You need to add support for ElectricCar and ChargingSpot in the existing parking system using the Iterator pattern. What is the minimum change to the current design to support this without modifying existing classes?
hard
A. Modify Vehicle and Spot classes to include electric-specific fields and logic, then update iterators accordingly.
B. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map; existing classes remain unchanged.
C. Create a new ElectricVehicle interface extending Vehicle and a ChargingSpot class, then modify existing iterators to handle them.
D. Add a flag isElectric to Vehicle class and modify SpotCompatibility to check this flag during iteration.

Solution

  1. Step 1: Identify extensibility requirements

    Adding ElectricCar and ChargingSpot should not require modifying existing classes to respect OCP.
  2. Step 2: Evaluate options

    Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map; existing classes remain unchanged. adds new enums and classes without modifying existing ones, preserving OCP. Modify Vehicle and Spot classes to include electric-specific fields and logic, then update iterators accordingly. modifies existing classes, violating OCP. Create a new ElectricVehicle interface extending Vehicle and a ChargingSpot class, then modify existing iterators to handle them. adds interfaces but requires modifying iterators, violating OCP. Add a flag isElectric to Vehicle class and modify SpotCompatibility to check this flag during iteration. modifies Vehicle class, violating OCP.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Adding new enums and subclasses without changing existing classes respects OCP [check]
Hint: Add new types via enums and subclasses, no existing class changes [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard