problemmediumooddesign-the-classes-and-data-structures-for-a-call-centerdesign the classes and data structures for a call centerdesigntheclassesanddatastructuresforacallcenterood-for-call-center-systemood for call center systemoodforcallcentersystem

OOD - Call Center

MediumUpdated: Jan 1, 2026

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:

  1. Route incoming calls to available employees at the appropriate rank (Fresher first)
  2. Maintain separate queues for calls requiring different ranks (Fresher, TL, PM level calls)
  3. Track employee availability in real-time (free/busy status)
  4. Support call escalation: Fresher → TL → PM
  5. Automatically assign next queued call to employee when they become available
  6. Handle multiple concurrent incoming calls (queue if all employees busy)
  7. Provide method getCallHandler(Call) to find appropriate available employee
  8. Implement dispatchCall(Call) to route calls or add to queue
  9. Support employee actions: receive call, complete call, escalate call
  10. Track call metrics: wait time, handling time, escalation count
  11. Support configuration: number of Freshers, presence of TL/PM
  12. Provide visibility: queue sizes, employee status, system statistics

Non-Functional Requirements:

  1. Thread Safety: Support concurrent call arrivals and employee state changes
  2. Performance: Route calls to available employees in <10ms
  3. Scalability: Handle 100+ concurrent calls in queues
  4. Fairness: Distribute calls evenly among employees at same rank
  5. 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, call handleCall(), 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 rank
  • CallHandler.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:

  • CallMetrics observes call lifecycle events
  • CallHandler notifies 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 free tracks 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.

Comments