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
WAITLISTED➔CONFIRMEDon 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 ===");
}
}