โฑ๏ธ 26 min read
Designing an Online Queue Management System
Difficulty: Intermediate Patterns: Strategy, Observer, State Asked at: TCS, PhonePe, Banks, Government portals
Functional Requirements
- Generate tokens - customers receive a numbered token with estimated wait time
- Multiple service counters - different counters handle different service types
- Priority queue - VIP/senior citizen tokens served before regular tokens
- Estimated wait time - calculate based on average service time and queue length
- Call next - counter operator calls the next token in queue
- Counter assignment - strategy to assign tokens to counters (shortest queue, round-robin)
Non-Functional Requirements
- Thread-safety - concurrent token generation and counter operations must be safe
- Fairness - within same priority, tokens are served in FIFO order
- Real-time updates - observers notified on token status changes for display boards
Core Entities
| Entity | Description |
|---|---|
Token |
Number, customer name, priority, status, service type |
TokenPriority |
Enum: REGULAR, SENIOR, VIP |
TokenStatus |
Enum: WAITING, SERVING, COMPLETED, CANCELLED |
ServiceType |
Enum: GENERAL, DEPOSIT, WITHDRAWAL, LOAN, ACCOUNT_OPENING |
Counter |
ID, service types it handles, current token being served |
CounterStatus |
Enum: OPEN, BUSY, CLOSED |
AssignmentStrategy |
Interface for token-to-counter routing |
QueueManager |
Central system managing tokens, counters, and assignment |
QueueObserver |
Notified on token called, completed, queue updates |
Class Diagram
classDiagram
class TokenPriority {
<<enumeration>>
REGULAR
SENIOR
VIP
}
class TokenStatus {
<<enumeration>>
WAITING
SERVING
COMPLETED
CANCELLED
}
class ServiceType {
<<enumeration>>
GENERAL
DEPOSIT
WITHDRAWAL
LOAN
ACCOUNT_OPENING
}
class CounterStatus {
<<enumeration>>
OPEN
BUSY
CLOSED
}
class Token {
-int number
-String customerName
-TokenPriority priority
-TokenStatus status
-ServiceType serviceType
-long createdAt
-long servedAt
-long completedAt
+getWaitTimeMs() long
}
class Counter {
-int id
-String name
-Set~ServiceType~ serviceTypes
-CounterStatus status
-Token currentToken
-int tokensServed
-double avgServiceTimeMs
+canServe(ServiceType) boolean
+assignToken(Token)
+completeService()
}
class AssignmentStrategy {
<<interface>>
+assignCounter(Token, List~Counter~) Counter
+getName() String
}
class ShortestQueueStrategy {
+assignCounter(Token, List~Counter~) Counter
}
class RoundRobinStrategy {
-int lastAssignedIndex
+assignCounter(Token, List~Counter~) Counter
}
class QueueObserver {
<<interface>>
+onTokenGenerated(Token token)
+onTokenCalled(Token token, Counter counter)
+onTokenCompleted(Token token, Counter counter)
+onTokenCancelled(Token token)
+onQueueUpdated(int queueSize)
}
class QueueManager {
-PriorityQueue~Token~ waitingQueue
-Map~Integer, Counter~ counters
-Map~Integer, Token~ tokenRegistry
-AssignmentStrategy strategy
-List~QueueObserver~ observers
-AtomicInteger tokenCounter
+generateToken(String name, TokenPriority, ServiceType) Token
+callNext(int counterId) Token
+completeService(int counterId)
+cancelToken(int tokenNumber)
+getEstimatedWaitTime(Token) long
+getQueueStatus() String
}
QueueManager --> Token
QueueManager --> Counter
QueueManager --> AssignmentStrategy
QueueManager --> QueueObserver
Token --> TokenPriority
Token --> TokenStatus
Token --> ServiceType
Counter --> CounterStatus
Counter --> ServiceType
AssignmentStrategy <|.. ShortestQueueStrategy
AssignmentStrategy <|.. RoundRobinStrategy
Design Patterns
| Pattern | Where | Why |
|---|---|---|
| Strategy | AssignmentStrategy with ShortestQueue/RoundRobin |
Swap counter assignment logic without changing QueueManager |
| Observer | QueueObserver notified on token events |
Display boards, SMS alerts, analytics decoupled from core |
| State | TokenStatus governs valid transitions |
Token lifecycle enforced (WAITINGโSERVINGโCOMPLETED) |
Data Structures
| Component | Structure | Why |
|---|---|---|
| Waiting queue | PriorityQueue<Token> ordered by priority then arrival time |
VIP/senior served first; within same priority, FIFO |
| Token registry | ConcurrentHashMap<Integer, Token> |
O(1) lookup by token number |
| Counters | ConcurrentHashMap<Integer, Counter> |
O(1) counter lookup |
| Per-counter queue | Optional Queue<Token> per counter |
Used by shortest-queue strategy |
| Observers | CopyOnWriteArrayList<QueueObserver> |
Thread-safe notification |
How It All Fits Together
Complete flow from token generation to service completion:
- Customer arrives โ calls
generateToken(name, priority, serviceType) - Token number assigned (atomic increment), status = WAITING
- Token added to priority queue (VIP > SENIOR > REGULAR, then FIFO within priority)
- Estimated wait time calculated: (position in queue ร avg service time)
- Observers notified โ display board shows new token
- Counter becomes free โ operator calls
callNext(counterId) - QueueManager picks highest priority token from queue
- AssignmentStrategy verifies counter can handle service type
- Token status โ SERVING, assigned to counter
- Observers notified โ display shows โToken X โ Counter Yโ
- Service done โ operator calls
completeService(counterId) - Token status โ COMPLETED, counter becomes OPEN for next
- Counter stats updated (tokens served, average service time)
Complete Code
All Classes
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;
// โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
enum TokenPriority {
REGULAR(0), SENIOR(1), VIP(2);
private final int level;
TokenPriority(int level) { this.level = level; }
public int getLevel() { return level; }
}
enum TokenStatus { WAITING, SERVING, COMPLETED, CANCELLED }
enum ServiceType { GENERAL, DEPOSIT, WITHDRAWAL, LOAN, ACCOUNT_OPENING }
enum CounterStatus { OPEN, BUSY, CLOSED }
// โโโ Token โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Token implements Comparable<Token> {
private final int number;
private final String customerName;
private final TokenPriority priority;
private final ServiceType serviceType;
private TokenStatus status;
private final long createdAt;
private long servedAt;
private long completedAt;
public Token(int number, String customerName, TokenPriority priority, ServiceType serviceType) {
this.number = number;
this.customerName = customerName;
this.priority = priority;
this.serviceType = serviceType;
this.status = TokenStatus.WAITING;
this.createdAt = System.currentTimeMillis();
}
public void markServing() {
this.status = TokenStatus.SERVING;
this.servedAt = System.currentTimeMillis();
}
public void markCompleted() {
this.status = TokenStatus.COMPLETED;
this.completedAt = System.currentTimeMillis();
}
public void markCancelled() {
this.status = TokenStatus.CANCELLED;
}
public long getWaitTimeMs() {
if (servedAt > 0) return servedAt - createdAt;
return System.currentTimeMillis() - createdAt;
}
public long getServiceTimeMs() {
if (completedAt > 0 && servedAt > 0) return completedAt - servedAt;
return 0;
}
@Override
public int compareTo(Token other) {
// Higher priority first
int pCompare = Integer.compare(other.priority.getLevel(), this.priority.getLevel());
if (pCompare != 0) return pCompare;
// Same priority: earlier token first (FIFO)
return Integer.compare(this.number, other.number);
}
public int getNumber() { return number; }
public String getCustomerName() { return customerName; }
public TokenPriority getPriority() { return priority; }
public ServiceType getServiceType() { return serviceType; }
public TokenStatus getStatus() { return status; }
public long getCreatedAt() { return createdAt; }
@Override
public String toString() {
return "Token[#" + number + " | " + customerName + " | " + priority +
" | " + serviceType + " | " + status + "]";
}
}
// โโโ Counter โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Counter {
private final int id;
private final String name;
private final Set<ServiceType> serviceTypes;
private CounterStatus status;
private Token currentToken;
private int tokensServed;
private double totalServiceTimeMs;
public Counter(int id, String name, ServiceType... types) {
this.id = id;
this.name = name;
this.serviceTypes = new HashSet<>(Arrays.asList(types));
this.status = CounterStatus.OPEN;
this.tokensServed = 0;
this.totalServiceTimeMs = 0;
}
public boolean canServe(ServiceType type) {
return serviceTypes.contains(type) && status != CounterStatus.CLOSED;
}
public boolean isOpen() { return status == CounterStatus.OPEN; }
public void assignToken(Token token) {
this.currentToken = token;
this.status = CounterStatus.BUSY;
token.markServing();
}
public Token completeService() {
if (currentToken == null) return null;
currentToken.markCompleted();
Token completed = currentToken;
tokensServed++;
totalServiceTimeMs += completed.getServiceTimeMs();
this.currentToken = null;
this.status = CounterStatus.OPEN;
return completed;
}
public void close() {
this.status = CounterStatus.CLOSED;
}
public double getAvgServiceTimeMs() {
return tokensServed > 0 ? totalServiceTimeMs / tokensServed : 60000; // default 1 min
}
public int getId() { return id; }
public String getName() { return name; }
public CounterStatus getStatus() { return status; }
public Token getCurrentToken() { return currentToken; }
public int getTokensServed() { return tokensServed; }
public Set<ServiceType> getServiceTypes() { return serviceTypes; }
@Override
public String toString() {
String serving = currentToken != null ? " | serving #" + currentToken.getNumber() : "";
return "Counter[" + name + " | " + status + serving +
" | served=" + tokensServed + "]";
}
}
// โโโ Assignment Strategy Interface โโโโโโโโโโโโโโโโโโโโโโ
interface AssignmentStrategy {
Counter assignCounter(Token token, List<Counter> counters);
String getName();
}
// โโโ Shortest Queue Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class ShortestQueueStrategy implements AssignmentStrategy {
@Override
public Counter assignCounter(Token token, List<Counter> counters) {
return counters.stream()
.filter(c -> c.canServe(token.getServiceType()))
.filter(Counter::isOpen)
.findFirst()
.orElse(null);
}
@Override
public String getName() { return "ShortestQueue"; }
}
// โโโ Round Robin Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class RoundRobinStrategy implements AssignmentStrategy {
private int lastIndex = -1;
@Override
public Counter assignCounter(Token token, List<Counter> counters) {
List<Counter> eligible = counters.stream()
.filter(c -> c.canServe(token.getServiceType()))
.filter(Counter::isOpen)
.collect(java.util.stream.Collectors.toList());
if (eligible.isEmpty()) return null;
lastIndex = (lastIndex + 1) % eligible.size();
return eligible.get(lastIndex);
}
@Override
public String getName() { return "RoundRobin"; }
}
// โโโ Queue Observer Interface โโโโโโโโโโโโโโโโโโโโโโโโโโโ
interface QueueObserver {
void onTokenGenerated(Token token);
void onTokenCalled(Token token, Counter counter);
void onTokenCompleted(Token token, Counter counter);
void onTokenCancelled(Token token);
void onQueueUpdated(int queueSize);
}
// โโโ Display Board Observer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class DisplayBoardObserver implements QueueObserver {
@Override
public void onTokenGenerated(Token token) {
System.out.println(" [DISPLAY] New token: #" + token.getNumber() +
" (" + token.getPriority() + ") - " + token.getCustomerName());
}
@Override
public void onTokenCalled(Token token, Counter counter) {
System.out.println(" [DISPLAY] >>> Token #" + token.getNumber() +
" โ " + counter.getName() + " <<<");
}
@Override
public void onTokenCompleted(Token token, Counter counter) {
System.out.println(" [DISPLAY] Token #" + token.getNumber() + " completed at " +
counter.getName());
}
@Override
public void onTokenCancelled(Token token) {
System.out.println(" [DISPLAY] Token #" + token.getNumber() + " cancelled");
}
@Override
public void onQueueUpdated(int queueSize) {
System.out.println(" [DISPLAY] Queue size: " + queueSize);
}
}
// โโโ Queue Manager โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class QueueManager {
private final PriorityQueue<Token> waitingQueue;
private final ConcurrentHashMap<Integer, Counter> counters;
private final ConcurrentHashMap<Integer, Token> tokenRegistry;
private AssignmentStrategy strategy;
private final List<QueueObserver> observers;
private final AtomicInteger tokenCounter;
private final ReentrantLock lock;
public QueueManager(AssignmentStrategy strategy) {
this.waitingQueue = new PriorityQueue<>();
this.counters = new ConcurrentHashMap<>();
this.tokenRegistry = new ConcurrentHashMap<>();
this.strategy = strategy;
this.observers = new CopyOnWriteArrayList<>();
this.tokenCounter = new AtomicInteger(0);
this.lock = new ReentrantLock();
}
public void addObserver(QueueObserver observer) { observers.add(observer); }
public void setStrategy(AssignmentStrategy s) { this.strategy = s; }
public void addCounter(Counter counter) { counters.put(counter.getId(), counter); }
public Token generateToken(String customerName, TokenPriority priority, ServiceType serviceType) {
lock.lock();
try {
int number = tokenCounter.incrementAndGet();
Token token = new Token(number, customerName, priority, serviceType);
waitingQueue.offer(token);
tokenRegistry.put(number, token);
observers.forEach(o -> o.onTokenGenerated(token));
observers.forEach(o -> o.onQueueUpdated(waitingQueue.size()));
return token;
} finally {
lock.unlock();
}
}
public Token callNext(int counterId) {
lock.lock();
try {
Counter counter = counters.get(counterId);
if (counter == null) throw new RuntimeException("Counter not found: " + counterId);
if (counter.getStatus() == CounterStatus.BUSY) {
throw new RuntimeException(counter.getName() + " is busy. Complete current service first.");
}
if (counter.getStatus() == CounterStatus.CLOSED) {
throw new RuntimeException(counter.getName() + " is closed.");
}
// Find next token this counter can serve
Token next = findNextForCounter(counter);
if (next == null) {
throw new RuntimeException("No tokens in queue for " + counter.getName());
}
waitingQueue.remove(next);
counter.assignToken(next);
observers.forEach(o -> o.onTokenCalled(next, counter));
observers.forEach(o -> o.onQueueUpdated(waitingQueue.size()));
return next;
} finally {
lock.unlock();
}
}
private Token findNextForCounter(Counter counter) {
// Find highest priority token that this counter can serve
for (Token token : waitingQueue) {
if (counter.canServe(token.getServiceType())) {
return token;
}
}
return null;
}
public void completeService(int counterId) {
lock.lock();
try {
Counter counter = counters.get(counterId);
if (counter == null) throw new RuntimeException("Counter not found");
Token completed = counter.completeService();
if (completed != null) {
observers.forEach(o -> o.onTokenCompleted(completed, counter));
}
} finally {
lock.unlock();
}
}
public void cancelToken(int tokenNumber) {
lock.lock();
try {
Token token = tokenRegistry.get(tokenNumber);
if (token == null) throw new RuntimeException("Token not found: " + tokenNumber);
if (token.getStatus() != TokenStatus.WAITING) {
throw new RuntimeException("Can only cancel WAITING tokens");
}
token.markCancelled();
waitingQueue.remove(token);
observers.forEach(o -> o.onTokenCancelled(token));
observers.forEach(o -> o.onQueueUpdated(waitingQueue.size()));
} finally {
lock.unlock();
}
}
public long getEstimatedWaitTime(Token token) {
if (token.getStatus() != TokenStatus.WAITING) return 0;
// Count tokens ahead with same or higher priority
long position = waitingQueue.stream()
.filter(t -> t.compareTo(token) < 0 || t.equals(token))
.count();
// Average service time across all relevant counters
double avgTime = counters.values().stream()
.filter(c -> c.canServe(token.getServiceType()))
.filter(c -> c.getStatus() != CounterStatus.CLOSED)
.mapToDouble(Counter::getAvgServiceTimeMs)
.average()
.orElse(60000); // default 1 minute
long eligibleCounters = counters.values().stream()
.filter(c -> c.canServe(token.getServiceType()))
.filter(c -> c.getStatus() != CounterStatus.CLOSED)
.count();
if (eligibleCounters == 0) return -1; // no counter available
return (long) ((position * avgTime) / eligibleCounters);
}
public void displayQueueStatus() {
System.out.println("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
System.out.println("โ QUEUE STATUS BOARD โ");
System.out.println("โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ");
System.out.println(" Waiting: " + waitingQueue.size() + " tokens");
System.out.println("\n Counters:");
for (Counter counter : counters.values()) {
System.out.println(" " + counter);
}
if (!waitingQueue.isEmpty()) {
System.out.println("\n Next in queue:");
int shown = 0;
for (Token t : waitingQueue) {
if (shown >= 5) break;
System.out.println(" " + t);
shown++;
}
if (waitingQueue.size() > 5) {
System.out.println(" ... and " + (waitingQueue.size() - 5) + " more");
}
}
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
}
public int getQueueSize() { return waitingQueue.size(); }
}
// โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
public class QueueManagementDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
System.out.println(" QUEUE MANAGEMENT - LLD DEMO ");
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
// Setup
QueueManager qm = new QueueManager(new ShortestQueueStrategy());
qm.addObserver(new DisplayBoardObserver());
// Create counters
Counter c1 = new Counter(1, "Counter-1", ServiceType.GENERAL, ServiceType.DEPOSIT, ServiceType.WITHDRAWAL);
Counter c2 = new Counter(2, "Counter-2", ServiceType.GENERAL, ServiceType.DEPOSIT, ServiceType.WITHDRAWAL);
Counter c3 = new Counter(3, "Counter-3", ServiceType.LOAN, ServiceType.ACCOUNT_OPENING);
Counter c4 = new Counter(4, "Counter-4", ServiceType.GENERAL, ServiceType.LOAN);
qm.addCounter(c1);
qm.addCounter(c2);
qm.addCounter(c3);
qm.addCounter(c4);
// โโโ Generate Tokens โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("--- Generating Tokens ---");
Token t1 = qm.generateToken("Rahul", TokenPriority.REGULAR, ServiceType.DEPOSIT);
Token t2 = qm.generateToken("Priya", TokenPriority.REGULAR, ServiceType.WITHDRAWAL);
Token t3 = qm.generateToken("Dr. Sharma", TokenPriority.VIP, ServiceType.GENERAL);
Token t4 = qm.generateToken("Amma", TokenPriority.SENIOR, ServiceType.DEPOSIT);
Token t5 = qm.generateToken("Arjun", TokenPriority.REGULAR, ServiceType.LOAN);
Token t6 = qm.generateToken("Meera", TokenPriority.REGULAR, ServiceType.GENERAL);
Token t7 = qm.generateToken("Col. Rathore", TokenPriority.VIP, ServiceType.ACCOUNT_OPENING);
qm.displayQueueStatus();
// โโโ Estimated Wait Times โโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Estimated Wait Times ---");
System.out.println(" Token #" + t1.getNumber() + " (Rahul): ~" +
(qm.getEstimatedWaitTime(t1) / 1000) + "s");
System.out.println(" Token #" + t3.getNumber() + " (Dr. Sharma VIP): ~" +
(qm.getEstimatedWaitTime(t3) / 1000) + "s");
System.out.println(" Token #" + t6.getNumber() + " (Meera): ~" +
(qm.getEstimatedWaitTime(t6) / 1000) + "s");
// โโโ Call Next at Counters โโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Calling Next ---");
qm.callNext(1); // Counter 1 calls (should get VIP - Dr. Sharma for GENERAL/DEPOSIT/WITHDRAWAL)
qm.callNext(2); // Counter 2 calls (Senior - Amma for DEPOSIT)
qm.callNext(3); // Counter 3 calls (VIP - Col. Rathore for ACCOUNT_OPENING)
qm.callNext(4); // Counter 4 calls (Arjun - LOAN)
qm.displayQueueStatus();
// โโโ Complete Service โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Completing Service ---");
Thread.sleep(100); // simulate some service time
qm.completeService(1);
qm.completeService(2);
// Counter 1 and 2 are now free, call next
System.out.println("\n--- Call Next Again ---");
qm.callNext(1); // should get Rahul (DEPOSIT - regular, first in)
qm.callNext(2); // should get Priya (WITHDRAWAL)
qm.displayQueueStatus();
// โโโ Cancel Token โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Cancel Token ---");
Token t8 = qm.generateToken("Late Customer", TokenPriority.REGULAR, ServiceType.GENERAL);
System.out.println(" Generated: " + t8);
qm.cancelToken(t8.getNumber());
// โโโ Counter Closed โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Close Counter 4 ---");
qm.completeService(4);
c4.close();
System.out.println(" Counter-4 closed.");
// โโโ Try Calling on Busy Counter โโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Error Cases ---");
try {
qm.callNext(1); // Counter 1 is BUSY (serving Rahul)
} catch (RuntimeException e) {
System.out.println(" Expected: " + e.getMessage());
}
try {
qm.callNext(4); // Counter 4 is CLOSED
} catch (RuntimeException e) {
System.out.println(" Expected: " + e.getMessage());
}
// โโโ Switch Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Switch to Round Robin Strategy ---");
qm.setStrategy(new RoundRobinStrategy());
qm.completeService(1);
qm.completeService(2);
qm.completeService(3);
// Generate more tokens
qm.generateToken("Customer A", TokenPriority.REGULAR, ServiceType.GENERAL);
qm.generateToken("Customer B", TokenPriority.REGULAR, ServiceType.GENERAL);
qm.callNext(1);
qm.callNext(2);
qm.displayQueueStatus();
System.out.println("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
System.out.println(" DEMO COMPLETE ");
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
}
}
import threading
import time
import heapq
from enum import Enum
from abc import ABC, abstractmethod
from typing import Optional
from dataclasses import dataclass, field
# โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TokenPriority(Enum):
REGULAR = 0
SENIOR = 1
VIP = 2
class TokenStatus(Enum):
WAITING = "WAITING"
SERVING = "SERVING"
COMPLETED = "COMPLETED"
CANCELLED = "CANCELLED"
class ServiceType(Enum):
GENERAL = "GENERAL"
DEPOSIT = "DEPOSIT"
WITHDRAWAL = "WITHDRAWAL"
LOAN = "LOAN"
ACCOUNT_OPENING = "ACCOUNT_OPENING"
class CounterStatus(Enum):
OPEN = "OPEN"
BUSY = "BUSY"
CLOSED = "CLOSED"
# โโโ Token โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Token:
def __init__(self, number: int, customer_name: str,
priority: TokenPriority, service_type: ServiceType):
self.number = number
self.customer_name = customer_name
self.priority = priority
self.service_type = service_type
self.status = TokenStatus.WAITING
self.created_at = time.time()
self.served_at = 0.0
self.completed_at = 0.0
def mark_serving(self):
self.status = TokenStatus.SERVING
self.served_at = time.time()
def mark_completed(self):
self.status = TokenStatus.COMPLETED
self.completed_at = time.time()
def mark_cancelled(self):
self.status = TokenStatus.CANCELLED
@property
def wait_time_ms(self) -> float:
if self.served_at > 0:
return (self.served_at - self.created_at) * 1000
return (time.time() - self.created_at) * 1000
@property
def service_time_ms(self) -> float:
if self.completed_at > 0 and self.served_at > 0:
return (self.completed_at - self.served_at) * 1000
return 0
def __lt__(self, other: "Token") -> bool:
# Higher priority first (higher enum value)
if self.priority.value != other.priority.value:
return self.priority.value > other.priority.value
# Same priority: earlier token first
return self.number < other.number
def __str__(self) -> str:
return (f"Token[#{self.number} | {self.customer_name} | "
f"{self.priority.name} | {self.service_type.name} | {self.status.name}]")
# โโโ Counter โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Counter:
def __init__(self, counter_id: int, name: str, service_types: list[ServiceType]):
self.id = counter_id
self.name = name
self.service_types = set(service_types)
self.status = CounterStatus.OPEN
self.current_token: Optional[Token] = None
self.tokens_served = 0
self.total_service_time_ms = 0.0
def can_serve(self, service_type: ServiceType) -> bool:
return service_type in self.service_types and self.status != CounterStatus.CLOSED
@property
def is_open(self) -> bool:
return self.status == CounterStatus.OPEN
def assign_token(self, token: Token):
self.current_token = token
self.status = CounterStatus.BUSY
token.mark_serving()
def complete_service(self) -> Optional[Token]:
if self.current_token is None:
return None
self.current_token.mark_completed()
completed = self.current_token
self.tokens_served += 1
self.total_service_time_ms += completed.service_time_ms
self.current_token = None
self.status = CounterStatus.OPEN
return completed
def close(self):
self.status = CounterStatus.CLOSED
@property
def avg_service_time_ms(self) -> float:
return self.total_service_time_ms / self.tokens_served if self.tokens_served > 0 else 60000
def __str__(self) -> str:
serving = f" | serving #{self.current_token.number}" if self.current_token else ""
return f"Counter[{self.name} | {self.status.name}{serving} | served={self.tokens_served}]"
# โโโ Assignment Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class AssignmentStrategy(ABC):
@abstractmethod
def assign_counter(self, token: Token, counters: list[Counter]) -> Optional[Counter]:
pass
@abstractmethod
def name(self) -> str: pass
class ShortestQueueStrategy(AssignmentStrategy):
def assign_counter(self, token: Token, counters: list[Counter]) -> Optional[Counter]:
eligible = [c for c in counters if c.can_serve(token.service_type) and c.is_open]
return eligible[0] if eligible else None
def name(self) -> str: return "ShortestQueue"
class RoundRobinStrategy(AssignmentStrategy):
def __init__(self):
self._last_index = -1
def assign_counter(self, token: Token, counters: list[Counter]) -> Optional[Counter]:
eligible = [c for c in counters if c.can_serve(token.service_type) and c.is_open]
if not eligible:
return None
self._last_index = (self._last_index + 1) % len(eligible)
return eligible[self._last_index]
def name(self) -> str: return "RoundRobin"
# โโโ Queue Observer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class QueueObserver(ABC):
@abstractmethod
def on_token_generated(self, token: Token): pass
@abstractmethod
def on_token_called(self, token: Token, counter: Counter): pass
@abstractmethod
def on_token_completed(self, token: Token, counter: Counter): pass
@abstractmethod
def on_token_cancelled(self, token: Token): pass
@abstractmethod
def on_queue_updated(self, queue_size: int): pass
class DisplayBoardObserver(QueueObserver):
def on_token_generated(self, token):
print(f" [DISPLAY] New token: #{token.number} ({token.priority.name}) - {token.customer_name}")
def on_token_called(self, token, counter):
print(f" [DISPLAY] >>> Token #{token.number} -> {counter.name} <<<")
def on_token_completed(self, token, counter):
print(f" [DISPLAY] Token #{token.number} completed at {counter.name}")
def on_token_cancelled(self, token):
print(f" [DISPLAY] Token #{token.number} cancelled")
def on_queue_updated(self, queue_size):
print(f" [DISPLAY] Queue size: {queue_size}")
# โโโ Queue Manager โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class QueueManager:
def __init__(self, strategy: AssignmentStrategy):
self._waiting_queue: list[Token] = [] # min-heap
self._counters: dict[int, Counter] = {}
self._token_registry: dict[int, Token] = {}
self._strategy = strategy
self._observers: list[QueueObserver] = []
self._token_counter = 0
self._lock = threading.Lock()
def add_observer(self, observer: QueueObserver):
self._observers.append(observer)
def set_strategy(self, strategy: AssignmentStrategy):
self._strategy = strategy
def add_counter(self, counter: Counter):
self._counters[counter.id] = counter
def generate_token(self, customer_name: str, priority: TokenPriority,
service_type: ServiceType) -> Token:
with self._lock:
self._token_counter += 1
token = Token(self._token_counter, customer_name, priority, service_type)
heapq.heappush(self._waiting_queue, token)
self._token_registry[token.number] = token
for obs in self._observers: obs.on_token_generated(token)
for obs in self._observers: obs.on_queue_updated(len(self._waiting_queue))
return token
def call_next(self, counter_id: int) -> Token:
with self._lock:
counter = self._counters.get(counter_id)
if not counter:
raise RuntimeError(f"Counter not found: {counter_id}")
if counter.status == CounterStatus.BUSY:
raise RuntimeError(f"{counter.name} is busy. Complete current service first.")
if counter.status == CounterStatus.CLOSED:
raise RuntimeError(f"{counter.name} is closed.")
next_token = self._find_next_for_counter(counter)
if not next_token:
raise RuntimeError(f"No tokens in queue for {counter.name}")
self._waiting_queue.remove(next_token)
heapq.heapify(self._waiting_queue)
counter.assign_token(next_token)
for obs in self._observers: obs.on_token_called(next_token, counter)
for obs in self._observers: obs.on_queue_updated(len(self._waiting_queue))
return next_token
def _find_next_for_counter(self, counter: Counter) -> Optional[Token]:
sorted_queue = sorted(self._waiting_queue)
for token in sorted_queue:
if counter.can_serve(token.service_type):
return token
return None
def complete_service(self, counter_id: int):
with self._lock:
counter = self._counters.get(counter_id)
if not counter:
raise RuntimeError("Counter not found")
completed = counter.complete_service()
if completed:
for obs in self._observers: obs.on_token_completed(completed, counter)
def cancel_token(self, token_number: int):
with self._lock:
token = self._token_registry.get(token_number)
if not token:
raise RuntimeError(f"Token not found: {token_number}")
if token.status != TokenStatus.WAITING:
raise RuntimeError("Can only cancel WAITING tokens")
token.mark_cancelled()
self._waiting_queue.remove(token)
heapq.heapify(self._waiting_queue)
for obs in self._observers: obs.on_token_cancelled(token)
for obs in self._observers: obs.on_queue_updated(len(self._waiting_queue))
def get_estimated_wait_time(self, token: Token) -> float:
if token.status != TokenStatus.WAITING:
return 0
position = sum(1 for t in self._waiting_queue if t <= token)
eligible = [c for c in self._counters.values()
if c.can_serve(token.service_type) and c.status != CounterStatus.CLOSED]
if not eligible:
return -1
avg_time = sum(c.avg_service_time_ms for c in eligible) / len(eligible)
return (position * avg_time) / len(eligible)
def display_queue_status(self):
print("\n+======================================+")
print("| QUEUE STATUS BOARD |")
print("+======================================+")
print(f" Waiting: {len(self._waiting_queue)} tokens")
print("\n Counters:")
for counter in self._counters.values():
print(f" {counter}")
if self._waiting_queue:
print("\n Next in queue:")
for t in sorted(self._waiting_queue)[:5]:
print(f" {t}")
if len(self._waiting_queue) > 5:
print(f" ... and {len(self._waiting_queue) - 5} more")
print("+======================================+")
@property
def queue_size(self) -> int:
return len(self._waiting_queue)
# โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
def main():
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
print(" QUEUE MANAGEMENT - LLD DEMO ")
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n")
qm = QueueManager(ShortestQueueStrategy())
qm.add_observer(DisplayBoardObserver())
c1 = Counter(1, "Counter-1", [ServiceType.GENERAL, ServiceType.DEPOSIT, ServiceType.WITHDRAWAL])
c2 = Counter(2, "Counter-2", [ServiceType.GENERAL, ServiceType.DEPOSIT, ServiceType.WITHDRAWAL])
c3 = Counter(3, "Counter-3", [ServiceType.LOAN, ServiceType.ACCOUNT_OPENING])
c4 = Counter(4, "Counter-4", [ServiceType.GENERAL, ServiceType.LOAN])
for c in [c1, c2, c3, c4]:
qm.add_counter(c)
print("--- Generating Tokens ---")
t1 = qm.generate_token("Rahul", TokenPriority.REGULAR, ServiceType.DEPOSIT)
t2 = qm.generate_token("Priya", TokenPriority.REGULAR, ServiceType.WITHDRAWAL)
t3 = qm.generate_token("Dr. Sharma", TokenPriority.VIP, ServiceType.GENERAL)
t4 = qm.generate_token("Amma", TokenPriority.SENIOR, ServiceType.DEPOSIT)
t5 = qm.generate_token("Arjun", TokenPriority.REGULAR, ServiceType.LOAN)
t6 = qm.generate_token("Meera", TokenPriority.REGULAR, ServiceType.GENERAL)
t7 = qm.generate_token("Col. Rathore", TokenPriority.VIP, ServiceType.ACCOUNT_OPENING)
qm.display_queue_status()
print("\n--- Estimated Wait Times ---")
print(f" Token #{t1.number} (Rahul): ~{qm.get_estimated_wait_time(t1)/1000:.0f}s")
print(f" Token #{t3.number} (Dr. Sharma VIP): ~{qm.get_estimated_wait_time(t3)/1000:.0f}s")
print("\n--- Calling Next ---")
qm.call_next(1)
qm.call_next(2)
qm.call_next(3)
qm.call_next(4)
qm.display_queue_status()
print("\n--- Completing Service ---")
time.sleep(0.1)
qm.complete_service(1)
qm.complete_service(2)
print("\n--- Call Next Again ---")
qm.call_next(1)
qm.call_next(2)
qm.display_queue_status()
print("\n--- Cancel Token ---")
t8 = qm.generate_token("Late Customer", TokenPriority.REGULAR, ServiceType.GENERAL)
qm.cancel_token(t8.number)
print("\n--- Close Counter 4 ---")
qm.complete_service(4)
c4.close()
print(" Counter-4 closed.")
print("\n--- Error Cases ---")
try:
qm.call_next(1)
except RuntimeError as e:
print(f" Expected: {e}")
try:
qm.call_next(4)
except RuntimeError as e:
print(f" Expected: {e}")
print("\n--- Switch to Round Robin ---")
qm.set_strategy(RoundRobinStrategy())
qm.complete_service(1)
qm.complete_service(2)
qm.complete_service(3)
qm.generate_token("Customer A", TokenPriority.REGULAR, ServiceType.GENERAL)
qm.generate_token("Customer B", TokenPriority.REGULAR, ServiceType.GENERAL)
qm.call_next(1)
qm.call_next(2)
qm.display_queue_status()
print("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
print(" DEMO COMPLETE ")
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
if __name__ == "__main__":
main()
#include <iostream>
#include <string>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <mutex>
#include <memory>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <thread>
#include <functional>
#include <stdexcept>
// โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
enum class TokenPriority { REGULAR = 0, SENIOR = 1, VIP = 2 };
enum class TokenStatus { WAITING, SERVING, COMPLETED, CANCELLED };
enum class ServiceType { GENERAL, DEPOSIT, WITHDRAWAL, LOAN, ACCOUNT_OPENING };
enum class CounterStatus { OPEN, BUSY, CLOSED };
std::string priorityStr(TokenPriority p) {
switch (p) { case TokenPriority::REGULAR: return "REGULAR"; case TokenPriority::SENIOR: return "SENIOR"; default: return "VIP"; }
}
std::string statusStr(TokenStatus s) {
switch (s) { case TokenStatus::WAITING: return "WAITING"; case TokenStatus::SERVING: return "SERVING"; case TokenStatus::COMPLETED: return "COMPLETED"; default: return "CANCELLED"; }
}
std::string serviceStr(ServiceType s) {
switch (s) { case ServiceType::GENERAL: return "GENERAL"; case ServiceType::DEPOSIT: return "DEPOSIT"; case ServiceType::WITHDRAWAL: return "WITHDRAWAL"; case ServiceType::LOAN: return "LOAN"; default: return "ACCOUNT_OPENING"; }
}
long long nowMs() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
}
// โโโ Token โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Token {
public:
int number;
std::string customerName;
TokenPriority priority;
ServiceType serviceType;
TokenStatus status = TokenStatus::WAITING;
long long createdAt, servedAt = 0, completedAt = 0;
Token(int num, std::string name, TokenPriority p, ServiceType st)
: number(num), customerName(std::move(name)), priority(p), serviceType(st), createdAt(nowMs()) {}
void markServing() { status = TokenStatus::SERVING; servedAt = nowMs(); }
void markCompleted() { status = TokenStatus::COMPLETED; completedAt = nowMs(); }
void markCancelled() { status = TokenStatus::CANCELLED; }
double serviceTimeMs() const { return completedAt > 0 ? (double)(completedAt - servedAt) : 0; }
// For priority comparison: higher priority value = serve first; same priority = lower number first
bool operator>(const Token& o) const {
if (static_cast<int>(priority) != static_cast<int>(o.priority))
return static_cast<int>(priority) < static_cast<int>(o.priority);
return number > o.number;
}
std::string toString() const {
return "Token[#" + std::to_string(number) + " | " + customerName + " | " +
priorityStr(priority) + " | " + serviceStr(serviceType) + " | " + statusStr(status) + "]";
}
};
// โโโ Counter โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Counter {
public:
int id;
std::string name;
std::unordered_set<int> serviceTypes; // store ServiceType as int
CounterStatus status = CounterStatus::OPEN;
Token* currentToken = nullptr;
int tokensServed = 0;
double totalServiceTimeMs = 0;
Counter(int id, std::string name, std::vector<ServiceType> types)
: id(id), name(std::move(name)) {
for (auto t : types) serviceTypes.insert(static_cast<int>(t));
}
bool canServe(ServiceType st) const {
return serviceTypes.count(static_cast<int>(st)) && status != CounterStatus::CLOSED;
}
bool isOpen() const { return status == CounterStatus::OPEN; }
void assignToken(Token* token) {
currentToken = token;
status = CounterStatus::BUSY;
token->markServing();
}
Token* completeService() {
if (!currentToken) return nullptr;
currentToken->markCompleted();
Token* completed = currentToken;
tokensServed++;
totalServiceTimeMs += completed->serviceTimeMs();
currentToken = nullptr;
status = CounterStatus::OPEN;
return completed;
}
void close() { status = CounterStatus::CLOSED; }
double avgServiceTimeMs() const {
return tokensServed > 0 ? totalServiceTimeMs / tokensServed : 60000;
}
std::string toString() const {
std::string serving = currentToken ? " | serving #" + std::to_string(currentToken->number) : "";
std::string st = status == CounterStatus::OPEN ? "OPEN" : (status == CounterStatus::BUSY ? "BUSY" : "CLOSED");
return "Counter[" + name + " | " + st + serving + " | served=" + std::to_string(tokensServed) + "]";
}
};
// โโโ Assignment Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class AssignmentStrategy {
public:
virtual ~AssignmentStrategy() = default;
virtual Counter* assignCounter(Token* token, std::vector<Counter*>& counters) = 0;
};
class ShortestQueueStrategy : public AssignmentStrategy {
public:
Counter* assignCounter(Token* token, std::vector<Counter*>& counters) override {
for (auto* c : counters) {
if (c->canServe(token->serviceType) && c->isOpen()) return c;
}
return nullptr;
}
};
class RoundRobinStrategy : public AssignmentStrategy {
int lastIdx = -1;
public:
Counter* assignCounter(Token* token, std::vector<Counter*>& counters) override {
std::vector<Counter*> eligible;
for (auto* c : counters) {
if (c->canServe(token->serviceType) && c->isOpen()) eligible.push_back(c);
}
if (eligible.empty()) return nullptr;
lastIdx = (lastIdx + 1) % eligible.size();
return eligible[lastIdx];
}
};
// โโโ Queue Manager โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class QueueManager {
std::priority_queue<Token*, std::vector<Token*>,
std::function<bool(Token*, Token*)>> waitingQueue;
std::unordered_map<int, Counter*> counters;
std::vector<std::unique_ptr<Counter>> counterStorage;
std::vector<std::unique_ptr<Token>> tokenStorage;
std::unordered_map<int, Token*> tokenRegistry;
std::shared_ptr<AssignmentStrategy> strategy;
std::atomic<int> tokenCounter{0};
std::mutex mtx;
public:
QueueManager(std::shared_ptr<AssignmentStrategy> strat)
: waitingQueue([](Token* a, Token* b) { return *a > *b; }),
strategy(std::move(strat)) {}
void setStrategy(std::shared_ptr<AssignmentStrategy> s) { strategy = std::move(s); }
void addCounter(std::unique_ptr<Counter> counter) {
Counter* ptr = counter.get();
counters[ptr->id] = ptr;
counterStorage.push_back(std::move(counter));
}
Token* generateToken(std::string name, TokenPriority prio, ServiceType st) {
std::lock_guard<std::mutex> lock(mtx);
int num = ++tokenCounter;
auto token = std::make_unique<Token>(num, std::move(name), prio, st);
Token* ptr = token.get();
waitingQueue.push(ptr);
tokenRegistry[num] = ptr;
tokenStorage.push_back(std::move(token));
std::cout << " [DISPLAY] New token: #" << num << " (" << priorityStr(prio) << ") - " << ptr->customerName << "\n";
return ptr;
}
Token* callNext(int counterId) {
std::lock_guard<std::mutex> lock(mtx);
Counter* counter = counters[counterId];
if (!counter) throw std::runtime_error("Counter not found");
if (counter->status == CounterStatus::BUSY)
throw std::runtime_error(counter->name + " is busy.");
if (counter->status == CounterStatus::CLOSED)
throw std::runtime_error(counter->name + " is closed.");
// Rebuild queue to find matching token
std::vector<Token*> temp;
Token* found = nullptr;
while (!waitingQueue.empty()) {
Token* t = waitingQueue.top();
waitingQueue.pop();
if (!found && counter->canServe(t->serviceType) && t->status == TokenStatus::WAITING) {
found = t;
} else {
temp.push_back(t);
}
}
for (auto* t : temp) waitingQueue.push(t);
if (!found) throw std::runtime_error("No tokens for " + counter->name);
counter->assignToken(found);
std::cout << " [DISPLAY] >>> Token #" << found->number << " -> " << counter->name << " <<<\n";
return found;
}
void completeService(int counterId) {
std::lock_guard<std::mutex> lock(mtx);
Counter* counter = counters[counterId];
if (!counter) return;
Token* completed = counter->completeService();
if (completed)
std::cout << " [DISPLAY] Token #" << completed->number << " completed at " << counter->name << "\n";
}
void cancelToken(int tokenNum) {
std::lock_guard<std::mutex> lock(mtx);
Token* token = tokenRegistry[tokenNum];
if (!token || token->status != TokenStatus::WAITING)
throw std::runtime_error("Cannot cancel");
token->markCancelled();
std::cout << " [DISPLAY] Token #" << tokenNum << " cancelled\n";
}
void displayStatus() {
std::cout << "\n+======================================+\n";
std::cout << "| QUEUE STATUS BOARD |\n";
std::cout << "+======================================+\n";
std::cout << " Counters:\n";
for (auto& c : counterStorage) std::cout << " " << c->toString() << "\n";
std::cout << "+======================================+\n";
}
};
// โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int main() {
std::cout << "=======================================\n";
std::cout << " QUEUE MANAGEMENT - LLD DEMO \n";
std::cout << "=======================================\n\n";
QueueManager qm(std::make_shared<ShortestQueueStrategy>());
qm.addCounter(std::make_unique<Counter>(1, "Counter-1",
std::vector<ServiceType>{ServiceType::GENERAL, ServiceType::DEPOSIT, ServiceType::WITHDRAWAL}));
qm.addCounter(std::make_unique<Counter>(2, "Counter-2",
std::vector<ServiceType>{ServiceType::GENERAL, ServiceType::DEPOSIT, ServiceType::WITHDRAWAL}));
qm.addCounter(std::make_unique<Counter>(3, "Counter-3",
std::vector<ServiceType>{ServiceType::LOAN, ServiceType::ACCOUNT_OPENING}));
qm.addCounter(std::make_unique<Counter>(4, "Counter-4",
std::vector<ServiceType>{ServiceType::GENERAL, ServiceType::LOAN}));
std::cout << "--- Generating Tokens ---\n";
qm.generateToken("Rahul", TokenPriority::REGULAR, ServiceType::DEPOSIT);
qm.generateToken("Priya", TokenPriority::REGULAR, ServiceType::WITHDRAWAL);
qm.generateToken("Dr. Sharma", TokenPriority::VIP, ServiceType::GENERAL);
qm.generateToken("Amma", TokenPriority::SENIOR, ServiceType::DEPOSIT);
qm.generateToken("Arjun", TokenPriority::REGULAR, ServiceType::LOAN);
qm.generateToken("Meera", TokenPriority::REGULAR, ServiceType::GENERAL);
qm.generateToken("Col. Rathore", TokenPriority::VIP, ServiceType::ACCOUNT_OPENING);
qm.displayStatus();
std::cout << "\n--- Calling Next ---\n";
qm.callNext(1);
qm.callNext(2);
qm.callNext(3);
qm.callNext(4);
qm.displayStatus();
std::cout << "\n--- Completing Service ---\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
qm.completeService(1);
qm.completeService(2);
std::cout << "\n--- Call Next Again ---\n";
qm.callNext(1);
qm.callNext(2);
qm.displayStatus();
std::cout << "\n--- Cancel Token ---\n";
auto* t8 = qm.generateToken("Late Customer", TokenPriority::REGULAR, ServiceType::GENERAL);
qm.cancelToken(t8->number);
std::cout << "\n--- Error Cases ---\n";
try { qm.callNext(1); }
catch (const std::runtime_error& e) { std::cout << " Expected: " << e.what() << "\n"; }
std::cout << "\n--- Switch to Round Robin ---\n";
qm.setStrategy(std::make_shared<RoundRobinStrategy>());
qm.completeService(1);
qm.completeService(2);
qm.completeService(3);
qm.completeService(4);
qm.generateToken("Customer A", TokenPriority::REGULAR, ServiceType::GENERAL);
qm.generateToken("Customer B", TokenPriority::REGULAR, ServiceType::GENERAL);
qm.callNext(1);
qm.callNext(2);
qm.displayStatus();
std::cout << "\n=======================================\n";
std::cout << " DEMO COMPLETE \n";
std::cout << "=======================================\n";
return 0;
}
State Transitions
stateDiagram-v2
[*] --> WAITING : token generated
WAITING --> SERVING : counter calls next
WAITING --> CANCELLED : customer leaves
SERVING --> COMPLETED : service finished
COMPLETED --> [*]
CANCELLED --> [*]
Sequence Diagram - Token Lifecycle
sequenceDiagram
participant Cust as Customer
participant QM as QueueManager
participant PQ as PriorityQueue
participant AS as AssignmentStrategy
participant Ctr as Counter
participant Obs as Observer
Cust->>QM: generateToken(name, priority, service)
QM->>PQ: offer(token)
QM->>Obs: onTokenGenerated(token)
QM-->>Cust: Token #N (WAITING)
Note over QM: Counter operator calls next
Ctr->>QM: callNext(counterId)
QM->>PQ: poll highest priority token
QM->>AS: verify counter can serve
QM->>Ctr: assignToken(token)
QM->>Obs: onTokenCalled(token, counter)
Note over Ctr: Service in progress
Ctr->>QM: completeService(counterId)
QM->>Ctr: completeService()
QM->>Obs: onTokenCompleted(token, counter)
How to Extend
| Extension | Implementation |
|---|---|
| SMS notifications | New SMSObserver implements QueueObserver sends message when token called |
| Digital display | Observer pushes real-time updates to WebSocket connected display |
| Appointment booking | Pre-assigned tokens with specific time slots (skip queue) |
| Counter specialization | Dynamic skill-based routing (e.g., only Hindi-speaking counters) |
| SLA tracking | Monitor wait times against targets; alert if breached |
| Multi-branch | Each branch has its own QueueManager; central dashboard aggregates |
| Token transfer | Allow transferring a token to a different service type mid-wait |
| Analytics | Track peak hours, avg wait time, counter utilization rates |
What Interviewers Look For
- โ Priority queue - VIP/senior served first, FIFO within same priority
- โ Strategy pattern for counter assignment (shortest queue vs round robin)
- โ State machine - token status transitions enforced
- โ Observer pattern for display board updates
- โ Service type routing - counter only serves compatible service types
- โ Thread-safety - concurrent token generation and counter operations
- โ Wait time estimation - position-based calculation with avg service time
- โ Clean separation - QueueManager doesnโt know about display/notification details
Related Concepts
Scale this design past a single process and these are the concepts it runs into:
- Performance Metrics โ โ wait-time estimation is Littleโs Law: queue length divided by service rate
- Load Balancing โ โ shortest-queue and round-robin counter assignment are the same two algorithms load balancers use
- WebSockets vs SSE โ โ display boards and customer phones need status pushed to them rather than polling for it
- Rate Limiting โ โ capping token issuance once the queue is already past what the counters can serve
Discussion
Newest first