Machine Coding Problem

Railway Booking System

macoAllcommercequota-logicwaitlisting
Commonly Asked By:AmtrakIRCTCTrainline

Functional Scope (In-Scope)

  • Hierarchical Quota Allocation Engine: Handles General, Tatkal, Ladies, and Foreign Quotas with automatic General pool fallbacks.
  • Waitlist/RAC State Machine Management: Holds waitlisted PNR entities and resolves active promotions automatically upon cancellation.
  • PNR lifecycle Transitioning: Enforces strict transitions from WAITLISTEDCONFIRMED on cancellations.
  • Dynamic Hourly Refund Engine: Automatically calculates refunds based on standard cancellation policies.

Explicit Boundaries (Out-of-Scope)

  • IRCTC Legacy Serial Sockets: Stubbed database calculations instead of live governmental mainframes integration.
  • Live GPS Telemetry Tracking: Skips dynamic mapping of trains on tracks and signaling networks.

Production reference implementations demonstrating waitlist promotions, quota allocations, and cancellation refunds in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

enum QuotaType {
    GENERAL, LADIES, TATKAL, FOREIGN_TOURIST
}

enum PNRStatus {
    CONFIRMED, WAITLISTED, CANCELLED
}

class Seat {
    private final String seatId;
    private final String coachNumber;
    private final int seatNumber;
    private final String berthType; // "UPPER", "LOWER", "MIDDLE", "SIDE_LOWER"

    public Seat(String seatId, String coachNumber, int seatNumber, String berthType) {
        this.seatId = seatId;
        this.coachNumber = coachNumber;
        this.seatNumber = seatNumber;
        this.berthType = berthType;
    }

    public String getSeatId() { return seatId; }
    public String getCoachNumber() { return coachNumber; }
    public int getSeatNumber() { return seatNumber; }
    public String getBerthType() { return berthType; }
}

class Passenger {
    private final String name;
    private final int age;
    private final String gender;

    public Passenger(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public String getGender() { return gender; }
}

class PNR {
    private final String pnrNumber;
    private final String trainId;
    private final String travelDate;
    private final List<Passenger> passengers;
    private final List<Seat> assignedSeats;
    private PNRStatus status;
    private double farePaid;
    private final QuotaType bookedUnderQuota;

    public PNR(String pnrNumber, String trainId, String travelDate, List<Passenger> passengers, QuotaType bookedUnderQuota) {
        this.pnrNumber = pnrNumber;
        this.trainId = trainId;
        this.travelDate = travelDate;
        this.passengers = passengers;
        this.assignedSeats = new CopyOnWriteArrayList<>();
        this.status = PNRStatus.WAITLISTED;
        this.bookedUnderQuota = bookedUnderQuota;
    }

    public String getPnrNumber() { return pnrNumber; }
    public String getTrainId() { return trainId; }
    public String getTravelDate() { return travelDate; }
    public List<Passenger> getPassengers() { return passengers; }
    public List<Seat> getAssignedSeats() { return assignedSeats; }
    public synchronized PNRStatus getStatus() { return status; }
    public synchronized void setStatus(PNRStatus status) { this.status = status; }
    public synchronized double getFarePaid() { return farePaid; }
    public synchronized void setFarePaid(double farePaid) { this.farePaid = farePaid; }
    public QuotaType getBookedUnderQuota() { return bookedUnderQuota; }
}

class RailwayBookingSystem {
    // Lock striping per train route and date: TrainId_TravelDate -> ReentrantLock
    private final ConcurrentHashMap<String, ReentrantLock> segmentLocks = new ConcurrentHashMap<>();
    
    // TrainID -> (TravelDate -> (QuotaType -> Count of Available Seats))
    private final ConcurrentHashMap<String, ConcurrentHashMap<String, ConcurrentHashMap<QuotaType, AtomicInteger>>> quotaInventory = new ConcurrentHashMap<>();
    
    // TrainID -> (TravelDate -> List of Free Seats)
    private final ConcurrentHashMap<String, ConcurrentHashMap<String, CopyOnWriteArrayList<Seat>>> availableSeatsMap = new ConcurrentHashMap<>();
    
    // TrainID -> (TravelDate -> List of Waitlisted PNRs)
    private final ConcurrentHashMap<String, ConcurrentHashMap<String, CopyOnWriteArrayList<PNR>>> waitlistQueues = new ConcurrentHashMap<>();
    
    private final ConcurrentHashMap<String, PNR> activeBookings = new ConcurrentHashMap<>();
    private final double baseFare = 500.0;

    private ReentrantLock getSegmentLock(String trainId, String date) {
        String key = trainId + "_" + date;
        return segmentLocks.computeIfAbsent(key, k -> new ReentrantLock());
    }

    public void initializeTrainRoute(String trainId, String date, List<Seat> totalSeats, Map<QuotaType, Integer> quotaDistribution) {
        ConcurrentHashMap<String, ConcurrentHashMap<QuotaType, AtomicInteger>> dateMap = quotaInventory.computeIfAbsent(trainId, k -> new ConcurrentHashMap<>());
        ConcurrentHashMap<QuotaType, AtomicInteger> quotaMap = dateMap.computeIfAbsent(date, k -> new ConcurrentHashMap<>());
        
        for (Map.Entry<QuotaType, Integer> entry : quotaDistribution.entrySet()) {
            quotaMap.put(entry.getKey(), new AtomicInteger(entry.getValue()));
        }

        availableSeatsMap.computeIfAbsent(trainId, k -> new ConcurrentHashMap<>()).put(date, new CopyOnWriteArrayList<>(totalSeats));
        waitlistQueues.computeIfAbsent(trainId, k -> new ConcurrentHashMap<>()).put(date, new CopyOnWriteArrayList<>());
    }

    // High performance striped check-and-book transactions
    public PNR bookTicket(String pnrNumber, String trainId, String date, List<Passenger> passengers, QuotaType preferredQuota) {
        int ticketCount = passengers.size();
        ReentrantLock lock = getSegmentLock(trainId, date);
        lock.lock();
        try {
            ConcurrentHashMap<QuotaType, AtomicInteger> quotas = quotaInventory.get(trainId).get(date);
            if (quotas == null) throw new IllegalArgumentException("Train segment not initialized.");

            AtomicInteger preferredStock = quotas.get(preferredQuota);
            AtomicInteger generalStock = quotas.get(QuotaType.GENERAL);

            QuotaType finalQuotaAllocated = preferredQuota;
            AtomicInteger allocatedStock = preferredStock;

            // Check if preferred quota has slots, fallback to General
            if (preferredStock == null || preferredStock.get() < ticketCount) {
                if (preferredQuota != QuotaType.GENERAL && generalStock != null && generalStock.get() >= ticketCount) {
                    finalQuotaAllocated = QuotaType.GENERAL;
                    allocatedStock = generalStock;
                } else {
                    // Quota exhausted -> Enqueue to Waitlist
                    PNR pnr = new PNR(pnrNumber, trainId, date, passengers, preferredQuota);
                    pnr.setFarePaid(calculateFare(preferredQuota, ticketCount));
                    pnr.setStatus(PNRStatus.WAITLISTED);
                    
                    waitlistQueues.get(trainId).get(date).add(pnr);
                    activeBookings.put(pnrNumber, pnr);
                    return pnr;
                }
            }

            // Deduct from allocation pool
            allocatedStock.addAndGet(-ticketCount);

            PNR pnr = new PNR(pnrNumber, trainId, date, passengers, finalQuotaAllocated);
            pnr.setStatus(PNRStatus.CONFIRMED);
            pnr.setFarePaid(calculateFare(finalQuotaAllocated, ticketCount));

            // Populate seat arrangements
            CopyOnWriteArrayList<Seat> freeSeats = availableSeatsMap.get(trainId).get(date);
            for (int i = 0; i < ticketCount; i++) {
                pnr.getAssignedSeats().add(freeSeats.remove(0));
            }

            activeBookings.put(pnrNumber, pnr);
            return pnr;
        } finally {
            lock.unlock();
        }
    }

    // Cancellation & Waitlist Promotion engine
    public double cancelTicket(String pnrNumber, LocalDateTime departureTime, LocalDateTime cancelTime) {
        PNR pnr = activeBookings.get(pnrNumber);
        if (pnr == null) {
            throw new IllegalArgumentException("Active booking not found.");
        }

        ReentrantLock lock = getSegmentLock(pnr.getTrainId(), pnr.getTravelDate());
        lock.lock();
        try {
            if (pnr.getStatus() == PNRStatus.CANCELLED) {
                throw new IllegalStateException("PNR already cancelled.");
            }

            double fareRefund = computeRefund(pnr.getFarePaid(), departureTime, cancelTime);
            PNRStatus oldStatus = pnr.getStatus();
            pnr.setStatus(PNRStatus.CANCELLED);

            if (oldStatus == PNRStatus.CONFIRMED) {
                List<Seat> releasedSeats = new ArrayList<>(pnr.getAssignedSeats());
                pnr.getAssignedSeats().clear();

                // Restore quota inventory
                quotaInventory.get(pnr.getTrainId()).get(pnr.getTravelDate())
                        .get(pnr.getBookedUnderQuota()).addAndGet(pnr.getPassengers().size());

                availableSeatsMap.get(pnr.getTrainId()).get(pnr.getTravelDate()).addAll(releasedSeats);

                // Run Waitlist promotion check
                processWaitlistPromotions(pnr.getTrainId(), pnr.getTravelDate());
            } else {
                // Remove from waitlist queue
                waitlistQueues.get(pnr.getTrainId()).get(pnr.getTravelDate()).remove(pnr);
            }

            return fareRefund;
        } finally {
            lock.unlock();
        }
    }

    private void processWaitlistPromotions(String trainId, String date) {
        CopyOnWriteArrayList<PNR> waitlist = waitlistQueues.get(trainId).get(date);
        CopyOnWriteArrayList<Seat> freeSeats = availableSeatsMap.get(trainId).get(date);
        ConcurrentHashMap<QuotaType, AtomicInteger> quotas = quotaInventory.get(trainId).get(date);

        Iterator<PNR> iterator = waitlist.iterator();
        while (iterator.hasNext() && !freeSeats.isEmpty()) {
            PNR waitlistedPNR = iterator.next();
            int requiredSeats = waitlistedPNR.getPassengers().size();
            AtomicInteger quotaCounter = quotas.get(waitlistedPNR.getBookedUnderQuota());

            if (quotaCounter != null && quotaCounter.get() >= requiredSeats) {
                // Confirmed!
                quotaCounter.addAndGet(-requiredSeats);
                waitlistedPNR.setStatus(PNRStatus.CONFIRMED);

                for (int i = 0; i < requiredSeats; i++) {
                    waitlistedPNR.getAssignedSeats().add(freeSeats.remove(0));
                }
                waitlist.remove(waitlistedPNR);
            }
        }
    }

    private double calculateFare(QuotaType quota, int passengerCount) {
        double multiplier = 1.0;
        if (quota == QuotaType.TATKAL) multiplier = 1.3;
        else if (quota == QuotaType.FOREIGN_TOURIST) multiplier = 1.5;
        return baseFare * multiplier * passengerCount;
    }

    private double computeRefund(double fare, LocalDateTime departure, LocalDateTime cancel) {
        long hoursDiff = ChronoUnit.HOURS.between(cancel, departure);
        if (hoursDiff >= 48) return fare * 0.90;
        else if (hoursDiff >= 24) return fare * 0.75;
        else if (hoursDiff >= 12) return fare * 0.50;
        return 0.0;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== STARTING RAILWAY RESERVATION SYSTEM SIMULATION ===");
        RailwayBookingSystem system = new RailwayBookingSystem();

        // Initialize coach seats
        List<Seat> seats = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            seats.add(new Seat("S" + i, "A1", i, i % 2 == 0 ? "LOWER" : "UPPER"));
        }

        // Configure quotas: 3 General, 2 Tatkal
        Map<QuotaType, Integer> quotas = new HashMap<>();
        quotas.put(QuotaType.GENERAL, 3);
        quotas.put(QuotaType.TATKAL, 2);

        system.initializeTrainRoute("express-101", "2026-06-05", seats, quotas);

        // 1. Concurrent Tatkal Rush Booking Attempt
        System.out.println("--- Testing Tatkal Rush Concurrency (2 spots available) ---");
        ExecutorService executor = Executors.newFixedThreadPool(5);
        CountDownLatch latch = new CountDownLatch(1);

        for (int i = 1; i <= 4; i++) {
            final String pnrNum = "PNR-00" + i;
            final String name = "User-" + i;
            executor.submit(() -> {
                try {
                    latch.await();
                    List<Passenger> plist = List.of(new Passenger(name, 30, "M"));
                    PNR pnr = system.bookTicket(pnrNum, "express-101", "2026-06-05", plist, QuotaType.TATKAL);
                    System.out.println("  Booking " + pnrNum + " state: " + pnr.getStatus() + " | Assigned seat counts: " + pnr.getAssignedSeats().size());
                } catch (Exception e) {
                    System.out.println("  Failed booking " + pnrNum + ": " + e.getMessage());
                }
            });
        }

        latch.countDown();
        executor.shutdown();
        executor.awaitTermination(2, TimeUnit.SECONDS);

        // 2. Cancellation & Auto-Promotion Test
        System.out.println("\n--- Cancelling confirmed ticket to trigger waitlist auto-promotion ---");
        LocalDateTime departure = LocalDateTime.of(2026, 6, 5, 20, 0);
        LocalDateTime cancelTime = LocalDateTime.of(2026, 6, 4, 20, 0); // 24 Hours before -> 75% refund

        // Find a confirmed Tatkal PNR
        String confirmedPNR = "PNR-001";
        System.out.println("Cancelling confirmed booking " + confirmedPNR + "...");
        double refund = system.cancelTicket(confirmedPNR, departure, cancelTime);
        System.out.println("  Refund processed: $" + refund);

        System.out.println("=== RAILWAY RESERVATION SYSTEM SIMULATION COMPLETE ===");
    }
}