Skip to main content

Problem Statement

Design a call center system that routes incoming calls to available employees based on a hierarchy, with escalation support when employees cannot handle calls.

Constraints and Assumptions

  • Employee levels: Operator, Supervisor, Director
  • Call routing: Operators always get initial calls
  • Escalation: If no operator is available or they can’t handle the call, escalate to supervisors, then directors
  • Director capability: Directors can handle all calls
  • Queuing: Calls are queued if nobody can answer
  • No VIP support: All calls have equal priority
  • Valid inputs: Assume inputs are valid

Design Overview

The call center system uses several classes implementing a hierarchy pattern:
  1. Rank: Enum defining employee levels
  2. Employee: Abstract base class for all employee types
  3. Operator, Supervisor, Director: Concrete employee implementations
  4. Call: Represents an incoming call
  5. CallCenter: Orchestrates call routing and queuing
This design uses the Chain of Responsibility pattern, where calls are passed through a hierarchy of handlers (operators → supervisors → directors) until one can handle it.

Implementation

Rank Enumeration

Defines the employee hierarchy:
from enum import Enum

class Rank(Enum):
    OPERATOR = 0
    SUPERVISOR = 1
    DIRECTOR = 2

Employee Base Class

Abstract class with common employee functionality:
from abc import ABCMeta, abstractmethod

class Employee(metaclass=ABCMeta):

    def __init__(self, employee_id, name, rank, call_center):
        self.employee_id = employee_id
        self.name = name
        self.rank = rank
        self.call = None
        self.call_center = call_center

    def take_call(self, call):
        """Assume the employee will always successfully take the call."""
        self.call = call
        self.call.employee = self
        self.call.state = CallState.IN_PROGRESS

    def complete_call(self):
        self.call.state = CallState.COMPLETE
        self.call_center.notify_call_completed(self.call)

    @abstractmethod
    def escalate_call(self):
        pass

    def _escalate_call(self):
        self.call.state = CallState.READY
        call = self.call
        self.call = None
        self.call_center.notify_call_escalated(call)

Concrete Employee Classes

Each level implements the escalation logic:
class Operator(Employee):

    def __init__(self, employee_id, name):
        super(Operator, self).__init__(employee_id, name, Rank.OPERATOR)

    def escalate_call(self):
        self.call.level = Rank.SUPERVISOR
        self._escalate_call()


class Supervisor(Employee):

    def __init__(self, employee_id, name):
        super(Supervisor, self).__init__(employee_id, name, Rank.SUPERVISOR)

    def escalate_call(self):
        self.call.level = Rank.DIRECTOR
        self._escalate_call()


class Director(Employee):

    def __init__(self, employee_id, name):
        super(Director, self).__init__(employee_id, name, Rank.DIRECTOR)

    def escalate_call(self):
        raise NotImplemented('Directors must be able to handle any call')

Call Classes

Represent call state and properties:
class CallState(Enum):
    READY = 0
    IN_PROGRESS = 1
    COMPLETE = 2


class Call(object):

    def __init__(self, rank):
        self.state = CallState.READY
        self.rank = rank
        self.employee = None

CallCenter Class

Orchestrates call routing and management:
from collections import deque

class CallCenter(object):

    def __init__(self, operators, supervisors, directors):
        self.operators = operators
        self.supervisors = supervisors
        self.directors = directors
        self.queued_calls = deque()

    def dispatch_call(self, call):
        if call.rank not in (Rank.OPERATOR, Rank.SUPERVISOR, Rank.DIRECTOR):
            raise ValueError('Invalid call rank: {}'.format(call.rank))
        employee = None
        if call.rank == Rank.OPERATOR:
            employee = self._dispatch_call(call, self.operators)
        if call.rank == Rank.SUPERVISOR or employee is None:
            employee = self._dispatch_call(call, self.supervisors)
        if call.rank == Rank.DIRECTOR or employee is None:
            employee = self._dispatch_call(call, self.directors)
        if employee is None:
            self.queued_calls.append(call)

    def _dispatch_call(self, call, employees):
        for employee in employees:
            if employee.call is None:
                employee.take_call(call)
                return employee
        return None

    def notify_call_escalated(self, call):  # ...
    def notify_call_completed(self, call):  # ...
    def dispatch_queued_call_to_newly_freed_employee(self, call, employee):  # ...

Key Design Patterns

Chain of Responsibility

Calls cascade through levels until handled:
Incoming Call

┌─────────────┐
│  Operators  │ → Available? → Take call
└─────────────┘      ↓ No

┌─────────────┐
│ Supervisors │ → Available? → Take call
└─────────────┘      ↓ No

┌─────────────┐
│  Directors  │ → Available? → Take call
└─────────────┘      ↓ No

┌─────────────┐
│    Queue    │
└─────────────┘

Abstract Factory Pattern

The Employee abstract base class defines the interface, with concrete implementations for each rank level.

Observer Pattern

The notify_call_completed() and notify_call_escalated() methods allow the call center to react to state changes.

Complexity Analysis

OperationTime ComplexityNotes
dispatch_call()O(n)Where n is the number of employees at each level
take_call()O(1)Direct assignment
escalate_call()O(1)State update and notification

Design Considerations

Advantages

  • Clear hierarchy: Well-defined escalation path
  • Flexible: Easy to add new employee types or ranks
  • Separation of concerns: Each class has a single responsibility
  • Extensible: Can add features like call priority or specialized routing

Call Flow

  1. Call arrives → Assigned OPERATOR rank initially
  2. Dispatch attempt → Try to find available operator
  3. Escalate if needed → Move up hierarchy
  4. Queue if all busy → Store in deque for later
  5. Complete call → Free employee for next call

Potential Improvements

  1. Priority queue: Support VIP or urgent calls
  2. Skills-based routing: Route calls based on employee expertise
  3. Load balancing: Distribute calls evenly among available employees
  4. Call statistics: Track metrics like wait time, handle time, escalation rate
  5. Automated callbacks: Call customers back when employee becomes available
  6. Concurrent handling: Use threading for simultaneous call processing
  7. Call recording: Log conversations for quality assurance

Build docs developers (and LLMs) love