Machine Coding Problem

Railway Booking System

macoAllcommercequota-logicwaitlisting
Commonly Asked By:AmtrakIRCTCTrainline

Requirements & System Scope

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 โž” CONFIRMED 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.

Class Diagram & Entity Relationships

Entity structure linking train seats, quotas, and waitlist systems:

Loading...
  • PNR Object: Tracks complete details including passengers, specific seat rows, current status, and total ticket cost.
  • Seat Record: Keeps specific layout data (Coach reference, row, seat type upper/lower).

Design Patterns & SOLID Principles

  • State Pattern: Controls transitions inside PNR states (WAITLISTED โž” CONFIRMED โž” CANCELLED) cleanly.
  • Strategy Pattern: Encapsulates custom refund rules depending on hourly intervals.
  • Single Responsibility Principle (SRP): Isolates segment seat layouts mapping from ticket pricing and refund pipelines.

Core Execution Workflows

Booking & Auto-Promotion Pipelines

  1. Quota Evaluation & Allocation:
    1. Receive preferred quota request (e.g. Ladies or Tatkal) and verify slot inventory.
    2. If slot is available, deduct count and associate specific seat IDs.
    3. If preferred slot is full, check General pool slots. If that is also full, push passenger into Waitlist queue.
  2. Waitlist Promotion on Cancellation:
    1. Release seats and restore quota stock on cancel requests.
    2. Evaluate first PNR waiting in the queue.
    3. If the released slot capacity satisfies the waiting group size, dequeue, allocate seats, and set PNR status to CONFIRMED.

Concurrency & Thread Safety Strategy

Ensuring high-frequency, thread-safe reservations in high-demand tatkal moments:

  • Deadlock-Free Segment locks: Segments locks per train and travel date using high-performance striping.
  • Copy-on-Write collections: Handles seat lists and waitlist queues using thread-safe, concurrent collections.

Complete Clean Code Blueprint

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

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...