OOD - Call Center
1. Problem Statement
A call center operates with a hierarchical employee structure where incoming customer calls must be intelligently routed to the appropriate employee based on availability and expertise level. The organization has three distinct employee ranks: Freshers (entry-level support representatives who handle routine inquiries), Technical Leads (TLs, experienced engineers who handle complex technical issues that Freshers cannot resolve), and Product Managers (PMs, senior staff who handle escalations, customer complaints, and strategic issues beyond the Technical Lead's scope). The system must handle multiple Freshers working simultaneously (typically 5-10 employees), but organizational constraints dictate having only one Technical Lead and one Product Manager available at any given time. When a call arrives, the system must first attempt to route it to any available Fresher; if all Freshers are busy, the call should be queued until one becomes free. When a Fresher receives a call but determines they cannot resolve the customer's issue (due to complexity, requiring elevated permissions, or being outside their knowledge domain), they must escalate the call to the Technical Lead, and similarly, the Technical Lead can escalate unresolvable calls to the Product Manager as a final escalation point.
The design must address several concurrent challenges that arise in real-world call center operations: managing multiple incoming calls arriving simultaneously and queuing them appropriately when all employees at the required rank are busy; tracking employee availability status in real-time to route calls to free employees immediately; implementing proper escalation workflows where a call's required rank increases as it moves up the hierarchy (Fresher → TL → PM); handling the scenario where an employee completes a call and should immediately be assigned the next waiting call from the queue if one exists; ensuring fair distribution of calls among employees at the same rank (not always assigning to the same Fresher); and providing visibility into system state such as how many calls are waiting in each queue, which employees are busy, and average wait times. The challenge is compounded by the need for thread-safety since calls can arrive concurrently, employees can finish calls at unpredictable times, and escalations can happen while new calls are arriving.
A robust object-oriented design must cleanly separate concerns using appropriate abstractions: an abstract Employee class capturing common attributes (name, rank, availability status) and behaviors (receiving calls, completing calls, escalating calls) that all employee types share, with concrete subclasses (Fresher, TechLead, ProductManager) that can override behavior if needed; a Call class representing an incoming call with attributes like caller ID, issue description, current required rank for handling, and timestamps for metrics; a CallHandler orchestrator that maintains queues for each rank level (one queue for calls requiring Freshers, one for TL-level calls, one for PM-level calls), tracks all employees and their current status, implements the call routing algorithm (getCallHandler method to find available employee at appropriate rank), and manages the lifecycle of calls from arrival through completion or escalation. The design must use appropriate data structures—priority queues or regular queues for waiting calls, lists or arrays for tracking employees by rank, and atomic boolean flags for employee availability—while implementing synchronization where necessary to prevent race conditions when multiple threads access shared state.
2. System Requirements
Functional Requirements:
- Route incoming calls to available employees at the appropriate rank (Fresher first)
- Maintain separate queues for calls requiring different ranks (Fresher, TL, PM level calls)
- Track employee availability in real-time (free/busy status)
- Support call escalation: Fresher → TL → PM
- Automatically assign next queued call to employee when they become available
- Handle multiple concurrent incoming calls (queue if all employees busy)
- Provide method
getCallHandler(Call)to find appropriate available employee - Implement
dispatchCall(Call)to route calls or add to queue - Support employee actions: receive call, complete call, escalate call
- Track call metrics: wait time, handling time, escalation count
- Support configuration: number of Freshers, presence of TL/PM
- Provide visibility: queue sizes, employee status, system statistics
Non-Functional Requirements:
- Thread Safety: Support concurrent call arrivals and employee state changes
- Performance: Route calls to available employees in <10ms
- Scalability: Handle 100+ concurrent calls in queues
- Fairness: Distribute calls evenly among employees at same rank
- Availability: System should never drop calls; always queue if needed
Assumptions:
- Fixed employee hierarchy: Fresher (rank 0) → TL (rank 1) → PM (rank 2)
- One call per employee at a time (no call conferencing)
- Calls are prioritized FIFO within each rank queue
- Employees manually escalate calls (system doesn't auto-escalate based on time)
- No employee breaks or shifts (simplified model)
3. Use Case Diagram
Actors:
- Caller: Customer initiating a phone call
- Fresher: Entry-level employee handling routine calls
- Technical Lead: Mid-level employee handling escalated technical calls
- Product Manager: Senior employee handling final escalations
- Call Center System: Automated routing and queue management
- Supervisor: Monitors system metrics and employee performance
Core Use Cases:
- Place Call
- Route Call to Employee
- Receive Call
- Handle Call
- Escalate Call
- Complete Call
- Get Next Queued Call
- View Queue Status
- View Employee Status
- View Call Metrics
graph TB subgraph CallCenterSystem["Call Center System"] UC1["Place Call"] UC2["Route Call"] UC3["Receive Call"] UC4["Handle Call"] UC5["Escalate Call"] UC6["Complete Call"] UC7["Get Next Call"] UC8["View Queue Status"] UC9["View Employee Status"] UC10["View Metrics"] end Caller([Caller]) Fresher([Fresher]) TL([Technical Lead]) PM([Product Manager]) System([Call Center System]) Supervisor([Supervisor]) Caller --> UC1 System --> UC2 System --> UC7 Fresher --> UC3 Fresher --> UC4 Fresher --> UC5 Fresher --> UC6 TL --> UC3 TL --> UC4 TL --> UC5 TL --> UC6 PM --> UC3 PM --> UC4 PM --> UC6 Supervisor --> UC8 Supervisor --> UC9 Supervisor --> UC10 UC1 -.->|triggers| UC2 UC2 -.->|assigns| UC3 UC3 -.->|starts| UC4 UC4 -.->|may trigger| UC5 UC4 -.->|may trigger| UC6 UC5 -.->|creates| UC2 UC6 -.->|triggers| UC7 style Caller fill:#FFC107,color:#000 style Fresher fill:#4CAF50,color:#fff style TL fill:#2196F3,color:#fff style PM fill:#9C27B0,color:#fff style System fill:#FF5722,color:#fff style Supervisor fill:#607D8B,color:#fff
4. Class Diagram
Core Classes:
- Employee (abstract): Base class for all employee types with rank, availability, call handling
- Fresher: Entry-level employee (rank 0)
- TechLead: Technical lead (rank 1)
- ProductManager: Product manager (rank 2)
- Call: Represents incoming call with rank requirement, caller info, timestamps
- CallHandler: Central orchestrator managing employees, queues, routing logic
- EmployeeRank: Enum (FRESHER, TECH_LEAD, PRODUCT_MANAGER)
- CallMetrics: Tracks performance metrics (wait time, handle time, escalations)
classDiagram class EmployeeRank { <<enumeration>> FRESHER(0) TECH_LEAD(1) PRODUCT_MANAGER(2) -int level +getLevel() int } class Employee { <<abstract>> -String id -String name -EmployeeRank rank -boolean free -Call currentCall #CallHandler callHandler +Employee(String, String, EmployeeRank, CallHandler) +receiveCall(Call) void +completeCall() void +escalateCall() void +isFree() boolean +getRank() EmployeeRank #handleCall(Call) void } class Fresher { +Fresher(String, String, CallHandler) } class TechLead { +TechLead(String, String, CallHandler) } class ProductManager { +ProductManager(String, String, CallHandler) } class Call { -String callId -String callerId -String issue -EmployeeRank requiredRank -long arrivalTime -long startTime -long endTime -int escalationCount +Call(String, String) +escalate() void +markStarted() void +markCompleted() void +getWaitTime() long +getHandleTime() long +reply(String) void +disconnect() void } class CallHandler { -static int LEVELS -List~Employee~[] employeeLevels -Queue~Call~[] callQueues -CallMetrics metrics -ReentrantLock lock +CallHandler() +registerEmployee(Employee) void +dispatchCall(Call) void +getCallHandler(Call) Employee +getNextCall(Employee) void +getQueueSize(EmployeeRank) int +getAvailableEmployees(EmployeeRank) int +getMetrics() CallMetrics } class CallMetrics { -AtomicLong totalCallsReceived -AtomicLong totalCallsCompleted -AtomicLong totalEscalations -AtomicLong totalWaitTimeMs -AtomicLong totalHandleTimeMs +recordCallReceived() void +recordCallCompleted(Call) void +recordEscalation() void +getAverageWaitTime() double +getAverageHandleTime() double +getEscalationRate() double } Employee <|-- Fresher Employee <|-- TechLead Employee <|-- ProductManager Employee --> EmployeeRank Employee --> Call Employee --> CallHandler Call --> EmployeeRank CallHandler "1" *-- "*" Employee CallHandler "1" *-- "*" Call CallHandler "1" --> "1" CallMetrics CallHandler --> EmployeeRank
5. Activity Diagrams
Incoming Call Routing
graph TD A([Call arrives]) --> B[Create Call object] B --> C[CallHandler.dispatchCall] C --> D[getCallHandler for required rank] D --> E{Available employee at rank?} E -->|Yes| F[Assign call to employee] F --> G[Employee.receiveCall] G --> H[Mark employee as busy] H --> I[Start call handling] I --> J([Employee handles call]) E -->|No| K{Check higher ranks?} K -->|Yes, TL/PM available| F K -->|No| L[Add to queue for rank] L --> M[Log wait metrics] M --> N([Call waits in queue])
Call Escalation
graph TD A([Employee cannot handle call]) --> B[Call.escalate] B --> C[Increment requiredRank] C --> D[Increment escalationCount] D --> E[Mark current employee free] E --> F[CallHandler.dispatchCall with new rank] F --> G{Employee at higher rank free?} G -->|Yes| H[Assign to higher rank employee] H --> I[Employee.receiveCall] I --> J([Higher rank handles call]) G -->|No| K[Add to higher rank queue] K --> L([Call waits for TL or PM]) E --> M[CallHandler.getNextCall for freed employee] M --> N{Calls in queue for employee's rank?} N -->|Yes| O[Assign next queued call] N -->|No| P([Employee remains idle])
Call Completion
graph TD A([Employee completes call]) --> B[Call.markCompleted] B --> C[Record metrics: wait, handle time] C --> D[Employee.completeCall] D --> E[Mark employee as free] E --> F[CallHandler.getNextCall] F --> G{Calls in queue for employee rank?} G -->|Yes| H[Dequeue next call] H --> I[Assign to employee] I --> J[Employee.receiveCall] J --> K([Employee handles next call]) G -->|No| L([Employee remains idle])
6. Java Implementation
Enums and Configuration
/**
* Employee rank hierarchy
*/
public enum EmployeeRank {
FRESHER(0),
TECH_LEAD(1),
PRODUCT_MANAGER(2);
private final int level;
EmployeeRank(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
public EmployeeRank nextRank() {
if (this == FRESHER) return TECH_LEAD;
if (this == TECH_LEAD) return PRODUCT_MANAGER;
return null; // PM is highest rank
}
@Override
public String toString() {
return name().replace("_", " ");
}
}
Call Class
import java.util.UUID;
/**
* Represents an incoming customer call
*/
public class Call {
private final String callId;
private final String callerId;
private final String issue;
private EmployeeRank requiredRank;
private final long arrivalTime;
private long startTime;
private long endTime;
private int escalationCount;
public Call(String callerId, String issue) {
this.callId = "CALL-" + UUID.randomUUID().toString().substring(0, 8);
this.callerId = callerId;
this.issue = issue;
this.requiredRank = EmployeeRank.FRESHER; // Always start with Fresher
this.arrivalTime = System.currentTimeMillis();
this.startTime = 0;
this.endTime = 0;
this.escalationCount = 0;
}
public String getCallId() {
return callId;
}
public String getCallerId() {
return callerId;
}
public String getIssue() {
return issue;
}
public EmployeeRank getRequiredRank() {
return requiredRank;
}
/**
* Escalate call to next rank
*/
public void escalate() {
EmployeeRank nextRank = requiredRank.nextRank();
if (nextRank != null) {
this.requiredRank = nextRank;
this.escalationCount++;
} else {
throw new IllegalStateException("Cannot escalate beyond Product Manager");
}
}
public void markStarted() {
this.startTime = System.currentTimeMillis();
}
public void markCompleted() {
this.endTime = System.currentTimeMillis();
}
/**
* Get wait time in milliseconds (arrival to start)
*/
public long getWaitTime() {
if (startTime == 0) {
return System.currentTimeMillis() - arrivalTime;
}
return startTime - arrivalTime;
}
/**
* Get handle time in milliseconds (start to end)
*/
public long getHandleTime() {
if (startTime == 0 || endTime == 0) {
return 0;
}
return endTime - startTime;
}
public int getEscalationCount() {
return escalationCount;
}
/**
* Send reply to caller (simulated)
*/
public void reply(String message) {
System.out.println(" [" + callId + "] Reply to " + callerId + ": " + message);
}
/**
* Disconnect call
*/
public void disconnect() {
markCompleted();
System.out.println(" [" + callId + "] Call disconnected");
}
@Override
public String toString() {
return String.format("Call[%s, Caller: %s, Rank: %s, Escalations: %d]",
callId, callerId, requiredRank, escalationCount);
}
}
Employee Classes
/**
* Abstract base class for all employees
*/
public abstract class Employee {
private final String id;
private final String name;
private final EmployeeRank rank;
private boolean free;
private Call currentCall;
protected final CallHandler callHandler;
public Employee(String id, String name, EmployeeRank rank, CallHandler callHandler) {
this.id = id;
this.name = name;
this.rank = rank;
this.callHandler = callHandler;
this.free = true;
this.currentCall = null;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public EmployeeRank getRank() {
return rank;
}
public synchronized boolean isFree() {
return free;
}
public Call getCurrentCall() {
return currentCall;
}
/**
* Receive incoming call
*/
public synchronized void receiveCall(Call call) {
if (!free) {
throw new IllegalStateException(name + " is already on a call");
}
this.currentCall = call;
this.free = false;
call.markStarted();
System.out.println("📞 " + rank + " " + name + " received " + call);
handleCall(call);
}
/**
* Template method for handling call (subclasses can override)
*/
protected void handleCall(Call call) {
// Default implementation - simulate work
call.reply("Hello, I'm " + name + ". How can I help you?");
}
/**
* Complete current call
*/
public synchronized void completeCall() {
if (currentCall == null) {
throw new IllegalStateException(name + " has no active call");
}
currentCall.disconnect();
System.out.println("✓ " + rank + " " + name + " completed " + currentCall.getCallId());
callHandler.recordCallCompleted(currentCall);
this.currentCall = null;
this.free = true;
// Check for next waiting call
callHandler.getNextCall(this);
}
/**
* Escalate current call to higher rank
*/
public synchronized void escalateCall() {
if (currentCall == null) {
throw new IllegalStateException(name + " has no active call to escalate");
}
System.out.println("⬆ " + rank + " " + name + " escalating " + currentCall.getCallId());
Call callToEscalate = currentCall;
callToEscalate.escalate();
this.currentCall = null;
this.free = true;
// Re-dispatch escalated call
callHandler.dispatchCall(callToEscalate);
// Check for next waiting call at this employee's level
callHandler.getNextCall(this);
}
@Override
public String toString() {
return String.format("%s %s [%s]", rank, name, free ? "Free" : "Busy");
}
}
/**
* Entry-level support representative
*/
public class Fresher extends Employee {
public Fresher(String id, String name, CallHandler callHandler) {
super(id, name, EmployeeRank.FRESHER, callHandler);
}
@Override
protected void handleCall(Call call) {
super.handleCall(call);
System.out.println(" Fresher handling routine inquiry...");
}
}
/**
* Technical lead handling escalated technical issues
*/
public class TechLead extends Employee {
public TechLead(String id, String name, CallHandler callHandler) {
super(id, name, EmployeeRank.TECH_LEAD, callHandler);
}
@Override
protected void handleCall(Call call) {
super.handleCall(call);
System.out.println(" Technical Lead handling complex technical issue...");
}
}
/**
* Product manager handling final escalations
*/
public class ProductManager extends Employee {
public ProductManager(String id, String name, CallHandler callHandler) {
super(id, name, EmployeeRank.PRODUCT_MANAGER, callHandler);
}
@Override
protected void handleCall(Call call) {
super.handleCall(call);
System.out.println(" Product Manager handling strategic escalation...");
}
}
Call Metrics
import java.util.concurrent.atomic.AtomicLong;
/**
* Tracks call center performance metrics
*/
public class CallMetrics {
private final AtomicLong totalCallsReceived = new AtomicLong(0);
private final AtomicLong totalCallsCompleted = new AtomicLong(0);
private final AtomicLong totalEscalations = new AtomicLong(0);
private final AtomicLong totalWaitTimeMs = new AtomicLong(0);
private final AtomicLong totalHandleTimeMs = new AtomicLong(0);
public void recordCallReceived() {
totalCallsReceived.incrementAndGet();
}
public void recordCallCompleted(Call call) {
totalCallsCompleted.incrementAndGet();
totalWaitTimeMs.addAndGet(call.getWaitTime());
totalHandleTimeMs.addAndGet(call.getHandleTime());
if (call.getEscalationCount() > 0) {
totalEscalations.addAndGet(call.getEscalationCount());
}
}
public long getTotalCallsReceived() {
return totalCallsReceived.get();
}
public long getTotalCallsCompleted() {
return totalCallsCompleted.get();
}
public long getTotalEscalations() {
return totalEscalations.get();
}
/**
* Get average wait time in seconds
*/
public double getAverageWaitTime() {
long completed = totalCallsCompleted.get();
return completed == 0 ? 0 : (totalWaitTimeMs.get() / 1000.0) / completed;
}
/**
* Get average handle time in seconds
*/
public double getAverageHandleTime() {
long completed = totalCallsCompleted.get();
return completed == 0 ? 0 : (totalHandleTimeMs.get() / 1000.0) / completed;
}
/**
* Get escalation rate (escalations per call)
*/
public double getEscalationRate() {
long completed = totalCallsCompleted.get();
return completed == 0 ? 0 : (double) totalEscalations.get() / completed;
}
@Override
public String toString() {
return String.format(
"Metrics{received=%d, completed=%d, avgWait=%.2fs, avgHandle=%.2fs, escalationRate=%.2f}",
totalCallsReceived.get(),
totalCallsCompleted.get(),
getAverageWaitTime(),
getAverageHandleTime(),
getEscalationRate()
);
}
}
CallHandler - Main Orchestrator
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
* Central call routing and queue management system
*/
public class CallHandler {
private static final int LEVELS = 3; // Fresher, TL, PM
// Employees organized by rank
private final List<Employee>[] employeeLevels;
// Call queues for each rank
private final Queue<Call>[] callQueues;
private final CallMetrics metrics;
private final ReentrantLock lock;
@SuppressWarnings("unchecked")
public CallHandler() {
// Initialize employee lists
employeeLevels = new ArrayList[LEVELS];
for (int i = 0; i < LEVELS; i++) {
employeeLevels[i] = new ArrayList<>();
}
// Initialize call queues
callQueues = new LinkedList[LEVELS];
for (int i = 0; i < LEVELS; i++) {
callQueues[i] = new LinkedList<>();
}
this.metrics = new CallMetrics();
this.lock = new ReentrantLock();
}
/**
* Register employee with the call handler
*/
public void registerEmployee(Employee employee) {
lock.lock();
try {
int level = employee.getRank().getLevel();
employeeLevels[level].add(employee);
System.out.println("Registered: " + employee);
} finally {
lock.unlock();
}
}
/**
* Find available employee for call at required rank or higher
*/
public Employee getCallHandler(Call call) {
lock.lock();
try {
int requiredLevel = call.getRequiredRank().getLevel();
// Try to find employee at required level or higher
for (int level = requiredLevel; level < LEVELS; level++) {
for (Employee emp : employeeLevels[level]) {
if (emp.isFree()) {
return emp;
}
}
}
return null; // No available employee
} finally {
lock.unlock();
}
}
/**
* Route call to available employee or add to queue
*/
public void dispatchCall(Call call) {
lock.lock();
try {
metrics.recordCallReceived();
Employee emp = getCallHandler(call);
if (emp != null) {
// Found available employee - assign immediately
emp.receiveCall(call);
} else {
// All employees busy - add to appropriate queue
int level = call.getRequiredRank().getLevel();
callQueues[level].offer(call);
System.out.println("⏸ " + call + " added to queue (size: " + callQueues[level].size() + ")");
}
} finally {
lock.unlock();
}
}
/**
* Assign next queued call to newly available employee
*/
public void getNextCall(Employee employee) {
lock.lock();
try {
int level = employee.getRank().getLevel();
// Check queue for this employee's level first
Queue<Call> queue = callQueues[level];
if (!queue.isEmpty()) {
Call nextCall = queue.poll();
System.out.println("▶ Assigning queued " + nextCall.getCallId() + " to " + employee.getName());
employee.receiveCall(nextCall);
}
// Could also check lower level queues if policy allows
} finally {
lock.unlock();
}
}
/**
* Record completed call metrics
*/
public void recordCallCompleted(Call call) {
metrics.recordCallCompleted(call);
}
/**
* Get queue size for specific rank
*/
public int getQueueSize(EmployeeRank rank) {
lock.lock();
try {
return callQueues[rank.getLevel()].size();
} finally {
lock.unlock();
}
}
/**
* Get number of available employees at rank
*/
public int getAvailableEmployees(EmployeeRank rank) {
lock.lock();
try {
int level = rank.getLevel();
return (int) employeeLevels[level].stream()
.filter(Employee::isFree)
.count();
} finally {
lock.unlock();
}
}
/**
* Get all employees at rank
*/
public List<Employee> getEmployeesAtRank(EmployeeRank rank) {
lock.lock();
try {
return new ArrayList<>(employeeLevels[rank.getLevel()]);
} finally {
lock.unlock();
}
}
public CallMetrics getMetrics() {
return metrics;
}
/**
* Display current system status
*/
public void displayStatus() {
lock.lock();
try {
System.out.println("\n╔════════════════════════════════════════════════╗");
System.out.println("║ CALL CENTER STATUS ║");
System.out.println("╠════════════════════════════════════════════════╣");
// Display employees by rank
for (EmployeeRank rank : EmployeeRank.values()) {
int level = rank.getLevel();
List<Employee> employees = employeeLevels[level];
int free = (int) employees.stream().filter(Employee::isFree).count();
int busy = employees.size() - free;
System.out.println(String.format("║ %-20s Free: %2d Busy: %2d ║",
rank, free, busy));
}
System.out.println("╠════════════════════════════════════════════════╣");
// Display queues
for (EmployeeRank rank : EmployeeRank.values()) {
int queueSize = callQueues[rank.getLevel()].size();
System.out.println(String.format("║ %-20s Queue: %3d ║",
rank + " Queue", queueSize));
}
System.out.println("╠════════════════════════════════════════════════╣");
System.out.println(String.format("║ Calls Received: %4d ║",
metrics.getTotalCallsReceived()));
System.out.println(String.format("║ Calls Completed: %4d ║",
metrics.getTotalCallsCompleted()));
System.out.println(String.format("║ Avg Wait Time: %5.2fs ║",
metrics.getAverageWaitTime()));
System.out.println(String.format("║ Escalation Rate: %5.2f ║",
metrics.getEscalationRate()));
System.out.println("╚════════════════════════════════════════════════╝\n");
} finally {
lock.unlock();
}
}
}
Demo Application
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* Demo simulating call center operations
*/
public class CallCenterDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("═══════════════════════════════════════════════");
System.out.println(" CALL CENTER SIMULATION");
System.out.println("═══════════════════════════════════════════════\n");
// Create call handler
CallHandler callHandler = new CallHandler();
// Register employees
System.out.println("▶ Setting up call center...\n");
// 5 Freshers
for (int i = 1; i <= 5; i++) {
callHandler.registerEmployee(
new Fresher("F" + i, "Fresher-" + i, callHandler)
);
}
// 1 Technical Lead
callHandler.registerEmployee(
new TechLead("TL1", "Tech-Lead-John", callHandler)
);
// 1 Product Manager
callHandler.registerEmployee(
new ProductManager("PM1", "PM-Sarah", callHandler)
);
System.out.println();
callHandler.displayStatus();
// Simulate incoming calls
System.out.println("▶ Simulating incoming calls...\n");
String[] callers = {"Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry"};
String[] issues = {
"Password reset",
"Billing question",
"Technical error",
"Account upgrade",
"Bug report",
"Feature request",
"Connection issue",
"Complaint"
};
Random random = new Random();
// Dispatch 8 calls
for (int i = 0; i < 8; i++) {
String caller = callers[i];
String issue = issues[i];
Call call = new Call(caller, issue);
System.out.println("📞 Incoming call from " + caller + ": " + issue);
callHandler.dispatchCall(call);
Thread.sleep(300);
}
System.out.println();
callHandler.displayStatus();
// Simulate some calls being completed
System.out.println("▶ Freshers handling calls...\n");
Thread.sleep(2000);
// Complete first 3 calls
for (Employee emp : callHandler.getEmployeesAtRank(EmployeeRank.FRESHER)) {
if (!emp.isFree()) {
emp.completeCall();
Thread.sleep(500);
}
}
System.out.println();
callHandler.displayStatus();
// Simulate escalation
System.out.println("▶ Simulating call escalation...\n");
Employee fresher = callHandler.getEmployeesAtRank(EmployeeRank.FRESHER).get(0);
if (!fresher.isFree()) {
System.out.println("Fresher cannot resolve issue, escalating...");
fresher.escalateCall();
Thread.sleep(1000);
}
System.out.println();
callHandler.displayStatus();
// TL escalates to PM
System.out.println("▶ TL escalating to PM...\n");
Employee tl = callHandler.getEmployeesAtRank(EmployeeRank.TECH_LEAD).get(0);
if (!tl.isFree()) {
System.out.println("Technical Lead escalating to Product Manager...");
tl.escalateCall();
Thread.sleep(1000);
}
System.out.println();
callHandler.displayStatus();
// PM completes escalated call
System.out.println("▶ PM resolving escalated call...\n");
Employee pm = callHandler.getEmployeesAtRank(EmployeeRank.PRODUCT_MANAGER).get(0);
if (!pm.isFree()) {
Thread.sleep(2000);
pm.completeCall();
}
// Complete remaining calls
System.out.println("\n▶ Completing remaining calls...\n");
Thread.sleep(2000);
for (EmployeeRank rank : EmployeeRank.values()) {
for (Employee emp : callHandler.getEmployeesAtRank(rank)) {
if (!emp.isFree()) {
emp.completeCall();
Thread.sleep(500);
}
}
}
System.out.println("\n▶ Final Status:");
callHandler.displayStatus();
System.out.println("Simulation complete! 📊");
System.out.println(callHandler.getMetrics());
}
}
7. Design Patterns Applied
1. Template Method Pattern (Employee.handleCall)
Intent: Define skeleton of call handling in base class, allow subclasses to customize.
Implementation:
Employee.receiveCall()provides template: mark busy, start call, callhandleCall(), etc.handleCall()is protected method that subclasses can override- Fresher, TechLead, ProductManager customize behavior
Benefits:
- Common logic (mark busy, metrics) in one place
- Subclasses only override what's different
- Enforces consistent call handling flow
public abstract class Employee {
public void receiveCall(Call call) {
// Template steps
this.currentCall = call;
this.free = false;
call.markStarted();
handleCall(call); // Hook for subclasses
}
protected void handleCall(Call call) {
// Default implementation
}
}
class Fresher extends Employee {
@Override
protected void handleCall(Call call) {
// Fresher-specific handling
}
}
2. Chain of Responsibility (Escalation)
Intent: Pass call up chain of handlers (Fresher → TL → PM) until resolved.
Implementation:
- Each employee can handle or escalate
Call.escalate()increases required rankCallHandler.dispatchCall()routes to appropriate level
Benefits:
- Decouples sender from receiver
- Dynamic chain based on availability
- Easy to add new ranks
employee.escalateCall(); // Passes to next handler
call.escalate(); // Increases rank requirement
callHandler.dispatchCall(call); // Re-routes to higher rank
3. Observer Pattern (Call Metrics)
Intent: Automatically record metrics when call events occur.
Implementation:
CallMetricsobserves call lifecycle eventsCallHandlernotifies metrics on call received/completed- Metrics update without employees knowing about tracking
Benefits:
- Separation of concerns (employees don't track metrics)
- Easy to add new metrics
- Centralized reporting
callHandler.dispatchCall(call);
// Internally: metrics.recordCallReceived()
employee.completeCall();
// Internally: callHandler.recordCallCompleted(call)
4. Strategy Pattern (Queue Management)
Intent: Encapsulate queuing strategy (could be FIFO, priority, etc).
Current: Simple FIFO queues Future: Could extract to QueueStrategy interface
interface QueueStrategy {
void enqueue(Call call);
Call dequeue();
}
class FIFOQueue implements QueueStrategy { ... }
class PriorityQueue implements QueueStrategy { ... }
5. Singleton Pattern (CallHandler)
Intent: Single instance managing all call routing (optional).
Could Implement:
public class CallHandler {
private static CallHandler instance;
public static synchronized CallHandler getInstance() {
if (instance == null) {
instance = new CallHandler();
}
return instance;
}
}
Benefits:
- Global access point
- Prevents multiple competing handlers
- Centralized state
6. State Pattern (Employee Free/Busy)
Intent: Employee behavior changes based on free/busy state.
Implementation:
boolean freetracks state- Methods check state before actions
- State transitions: free → busy → free
public synchronized void receiveCall(Call call) {
if (!free) {
throw new IllegalStateException("Already on a call");
}
this.free = false; // State transition
}
public synchronized void completeCall() {
this.free = true; // State transition
}
8. Key Design Decisions
Decision 1: Abstract Employee Base Class
Rationale: All employees share common attributes (name, rank, availability) and behaviors (receive, complete, escalate).
Benefits:
- Code reuse for common functionality
- Polymorphic handling (List)
- Easy to add new employee types
Alternative Considered: Interface would require duplicating common code in each implementation.
Decision 2: Separate Queue Per Rank Level
Rationale: Maintain three queues (Fresher, TL, PM) instead of one global queue.
Benefits:
- Fair queuing per level
- Can prioritize high-rank calls
- Escalated calls don't go to back of global queue
Implementation:
Queue<Call>[] callQueues = new LinkedList[LEVELS];
// callQueues[0] = Fresher queue
// callQueues[1] = TL queue
// callQueues[2] = PM queue
Decision 3: Call Object Tracks Required Rank
Rationale: Call knows what rank it needs (requiredRank field).
Benefits:
- Self-contained escalation:
call.escalate()increments rank - Easy to route: check call.requiredRank vs employee.rank
- Call history tracks escalations
call.getRequiredRank(); // Returns FRESHER initially
call.escalate(); // Now requires TECH_LEAD
Decision 4: Synchronized Employee Methods
Rationale: Employee state (free/busy, currentCall) accessed by multiple threads.
Benefits:
- Thread-safe state transitions
- Prevents assigning call to busy employee
- Prevents double-completion
public synchronized void receiveCall(Call call) {
if (!free) throw new IllegalStateException();
this.free = false;
}
Decision 5: Automatic Next Call Assignment
Rationale: When employee completes call, automatically check queue for next call.
Benefits:
- Maximizes employee utilization
- Reduces wait time
- No manual "pull" needed
public void completeCall() {
// ... complete current call
this.free = true;
callHandler.getNextCall(this); // Automatically assign next
}
Decision 6: CallHandler Owns Employee Lists
Rationale: CallHandler maintains employeeLevels[rank] lists instead of employees registering themselves.
Benefits:
- Centralized employee tracking
- Easy to query available employees
- Single source of truth
Employee emp = getCallHandler(call);
// Searches employeeLevels for available employee
Decision 7: Metrics with AtomicLong
Rationale: Use AtomicLong counters instead of synchronized incrementing.
Benefits:
- Lock-free updates (better performance)
- Thread-safe without explicit synchronization
- Simple API
totalCallsReceived.incrementAndGet(); // Atomic
totalWaitTimeMs.addAndGet(waitTime); // Atomic
Decision 8: Call Lifecycle Timestamps
Rationale: Track arrivalTime, startTime, endTime in Call object.
Benefits:
- Calculate wait time (start - arrival)
- Calculate handle time (end - start)
- Accurate metrics
call.markStarted(); // Records startTime
call.markCompleted(); // Records endTime
long wait = call.getWaitTime(); // Calculated
Decision 9: Escalation Increments Count
Rationale: Call.escalationCount tracks how many times call was escalated.
Benefits:
- Identify problematic calls
- Metric for first-call resolution rate
- Audit trail
call.escalate();
// Increments escalationCount
// Changes requiredRank to next level
Decision 10: ReentrantLock in CallHandler
Rationale: Use explicit lock instead of synchronized methods.
Benefits:
- Fine-grained lock control
- Can hold lock across multiple operations
- Try-lock capability (not used here but available)
lock.lock();
try {
Employee emp = getCallHandler(call);
if (emp != null) {
emp.receiveCall(call);
} else {
callQueues[level].offer(call);
}
} finally {
lock.unlock();
}
This design creates a robust call center system with proper separation of concerns (employees handle calls, CallHandler routes calls, CallMetrics tracks performance), thread-safe concurrent operations, and flexible escalation workflows. The architecture supports evolution to more sophisticated routing algorithms, priority queuing, skill-based routing, and integration with telephony systems.