Limited time: AI code review, hints, mock interviews, whiteboard analysis, and all Pro features are unlocked. Enroll
โฑ๏ธ 26 min read

Designing an Online Queue Management System

Difficulty: Intermediate Patterns: Strategy, Observer, State Asked at: TCS, PhonePe, Banks, Government portals


Functional Requirements

  1. Generate tokens - customers receive a numbered token with estimated wait time
  2. Multiple service counters - different counters handle different service types
  3. Priority queue - VIP/senior citizen tokens served before regular tokens
  4. Estimated wait time - calculate based on average service time and queue length
  5. Call next - counter operator calls the next token in queue
  6. Counter assignment - strategy to assign tokens to counters (shortest queue, round-robin)

Non-Functional Requirements

  1. Thread-safety - concurrent token generation and counter operations must be safe
  2. Fairness - within same priority, tokens are served in FIFO order
  3. 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:

  1. Customer arrives โ†’ calls generateToken(name, priority, serviceType)
  2. Token number assigned (atomic increment), status = WAITING
  3. Token added to priority queue (VIP > SENIOR > REGULAR, then FIFO within priority)
  4. Estimated wait time calculated: (position in queue ร— avg service time)
  5. Observers notified โ†’ display board shows new token
  6. Counter becomes free โ†’ operator calls callNext(counterId)
  7. QueueManager picks highest priority token from queue
  8. AssignmentStrategy verifies counter can handle service type
  9. Token status โ†’ SERVING, assigned to counter
  10. Observers notified โ†’ display shows โ€œToken X โ†’ Counter Yโ€
  11. Service done โ†’ operator calls completeService(counterId)
  12. Token status โ†’ COMPLETED, counter becomes OPEN for next
  13. 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

  1. โœ… Priority queue - VIP/senior served first, FIFO within same priority
  2. โœ… Strategy pattern for counter assignment (shortest queue vs round robin)
  3. โœ… State machine - token status transitions enforced
  4. โœ… Observer pattern for display board updates
  5. โœ… Service type routing - counter only serves compatible service types
  6. โœ… Thread-safety - concurrent token generation and counter operations
  7. โœ… Wait time estimation - position-based calculation with avg service time
  8. โœ… Clean separation - QueueManager doesnโ€™t know about display/notification details


Scale this design past a single process and these are the concepts it runs into:

Discussion

Newest first
You

Free system design + DSA prep. If it helped you crack an interview, consider supporting.

SensAI SensAI
Beta
Listening...
Tap mic to stop voice mode

Shape what we build next

Every piece of feedback is read by the team and directly influences our roadmap.

What type of feedback?

Install SystemCraft

Add to your home screen for instant access, offline reading, and a distraction-free experience.

Offline reading Faster loads No browser tabs App-like feel

Unlock AI Features

One click to activate - no payment, no credit card. Just sign in and you're in.

AI code review and hints
SensAI chat assistant
AI mock interviews
Whiteboard analysis
100% free during early access