parking_lot_system.md

šŸ§ Multi-Storey Shopping Mall Parking Lot — Low-Level Design & Real-World Simulation

Target Interview Level: SDE-1 Fresher / Campus Placement — Juspay-specific
Topic: Multi-Storey Parking Lot Management System with Flexible Dual-Capacity Slots & Smart Allocation
Language: Clean, Simple C++ (Raw pointers, no complex smart pointers, no multithreading overhead)
Evaluation Focus: Live problem decomposition, handling edge cases, clean OOP modeling, and a complete working simulation (beginner-friendly and whiteboard-ready).


🧭 Interview Mindset: How Juspay Evaluates SDE-1 LLD

In a Juspay campus or fresher interview, interviewers do not award points for reciting Gang-of-Four pattern names or writing overly abstract modern C++ templates. Instead, they look for:

  1. How you clarify ambiguous constraints before writing a single line of code.
  2. How intuitively your classes model real-world physical objects (floors, sections, slots, cars, bikes).
  3. How rigorously you think through edge cases (e.g., dual-capacity slot sharing, mall full, vehicle mismatch, payment failures).
  4. How you explain logic out loud in plain English before coding methods like park() or unpark().
  5. A complete, runnable mental model / simulation that proves your code actually works.

Step 1: Clarifying Requirements First (Thinking Out Loud)

When the interviewer says: "Design a multi-storey shopping mall parking lot", start by driving the conversation to lock down the exact problem scope.

The Live Dialogue:

Candidate: "Before jumping into classes, let me make sure I have all the physical and operational constraints mapped out:

  1. Mall Physical Layout: How many floors are there, how are they subdivided, and what is the total capacity?
  2. Vehicle Types & Slot Sharing: What vehicles are permitted, and can smaller vehicles share larger bays?
  3. Entry & Spot Selection: Does the system suggest the best/nearest spot, or can the driver park anywhere?
  4. Tariff & Exit Billing: What is the pricing model, how do we treat partial hours, and what payment modes must be supported?
  5. Execution Scope: Should we focus on clean in-memory OOP domain logic, data structures, and edge cases first?"

The Locked-In Requirements:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ SHOPPING MALL PARKING LOT │ │ 5 Floors (Floor 1 to Floor 5) │ │ Each Floor has 4 Sections: [A, B, C, D] │ │ Each Section has 10 Car Slots │ │ Total Capacity: 200 Car-Equivalent Slots │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
  1. Hierarchy & Dimensions:

    • 5 Floors (1 to 5).
    • Each floor has 4 Sections: A, B, C, D.
    • Each section has 10 Car Slots (Numbered 1 to 10).
    • Total base slots across the entire mall = $5 \times 4 \times 10 = 200\text{ car slots}$.
  2. Dual-Capacity / Slot-Sharing Rule:

    • A standard slot fits 1 Car.
    • Special Rule: That same slot can instead hold up to 4 Motorcycles.
    • Mutual Exclusion: If a car is inside, 0 motorcycles can enter. If even 1 motorcycle is inside, a car cannot enter, but up to 3 additional motorcycles can join until the slot holds 4.
  3. Smart Spot Allocation (Best Available Spot):

    • Base heuristic: Proximity to entrance $\to$ Lowest floor first (1 $\to$ 5), closest section (A $\to$ D), lowest slot number (1 $\to$ 10).
    • Motorcycle Packing Optimization: When a motorcycle enters, the system should first look for slots already holding 1 to 3 motorcycles to pack them together. Only if no partially occupied bike slot exists should it take a fresh, empty car slot. This prevents motorcycles from unnecessarily consuming pristine car bays!
  4. Tariff & Payment:

    • Standard rate: ₹20 per hour.
    • Partial hours are rounded up (ceiling calculation, e.g. 65 minutes = 2 hours = ₹40; minimum charge = 1 hour = ₹20).
    • Exit checkout supports multiple payment modes: UPI, Credit/Debit Card, and Cash.
  5. Operational Scope:

    • Clean, direct in-memory simulation focusing purely on object modeling, capacity tracking, and pricing rules.

Step 2: Core Entities & Key Attributes

Here is how we map physical mall concepts into clean C++ classes:

1. Vehicle (Abstract Base)

  • Attributes:
    • licensePlate (string): Unique registration number.
    • type (VehicleType enum: CAR, MOTORCYCLE).
  • Responsibility: Identifies the vehicle and defines how many capacity units it requires (Car = 4 units, Motorcycle = 1 unit).

2. Car & Motorcycle (Derived)

  • Simple, concrete vehicle classes with fixed capacity requirements.

3. ParkingSlot

  • Attributes:
    • slotId (string, e.g., "F1-A-S03").
    • floorNumber (int), sectionName (char), slotNumber (int).
    • parkedVehicles (vector<Vehicle*>): Holds either 1 Car OR up to 4 Motorcycles.
  • Responsibility: Manages physical occupancy, enforces the 1-car vs 4-motorcycle constraint, and answers whether a given vehicle can fit.

4. Section

  • Attributes:
    • name (char: 'A', 'B', 'C', 'D').
    • slots (vector<ParkingSlot*>): 10 slots.
  • Responsibility: Groups contiguous slots within a floor.

5. Floor

  • Attributes:
    • floorNumber (int: 1 to 5).
    • sections (map<char, Section>): Maps 'A', 'B', 'C', 'D' to their respective section objects.
  • Responsibility: Aggregates sections and exposes all floor slots for queries.

6. ParkingTicket

  • Attributes:
    • ticketId (string, e.g., "TKT-1001").
    • vehicle (Vehicle*).
    • allocatedSlotId (string).
    • entryTime (system_clock::time_point).
    • exitTime (system_clock::time_point).
    • feeCharged (double).
    • status (TicketStatus enum: ACTIVE, PAID, CANCELLED).
  • Responsibility: An audit receipt proving lawful entry, spot allocation, and payment clearance.

7. ParkingLot (Orchestrator / Facade)

  • Attributes:
    • floors (vector<Floor>): 5 floors.
    • slotLookup (map<string, ParkingSlot*>): Quick $O(1)$ lookup from slotId to slot.
    • activeTickets (map<string, ParkingTicket*>): In-memory store of vehicles currently inside.
    • allocator (SpotAllocator*): Plug-in spot discovery engine.
    • feeCalculator (FeeCalculator*): Plug-in tariff calculation engine.
  • Responsibility: Coordinates park(), unpark(), calculateFare(), and live display board metrics.

Step 3: Class Relationships & Architecture

Composition, Inheritance & Interfaces

  1. Composition (Has-A / Tree Hierarchy):

    • ParkingLot contains 5 Floor objects.
    • Each Floor contains 4 Section objects (A, B, C, D).
    • Each Section contains 10 ParkingSlot pointers.
    • Each ParkingSlot holds 0 to 4 Vehicle pointers.
    • ParkingLot maintains active ParkingTicket pointers.
  2. Inheritance (Is-A):

    • Car is-a Vehicle.
    • Motorcycle is-a Vehicle.
  3. Abstract Contracts / Extensibility Points (Polymorphism):

    • SpotAllocator (Interface) $\to$ implemented by MallOptimalSpotAllocator.
    • FeeCalculator (Interface) $\to$ implemented by MallHourlyFeeCalculator.
    • PaymentProcessor (Interface) $\to$ implemented by UPIPaymentProcessor, CardPaymentProcessor, CashPaymentProcessor.
classDiagram class Vehicle { <<abstract>> #string licensePlate #VehicleType type +getLicensePlate() string +getType() VehicleType +getCapacityUnits()* int } class Car { +getCapacityUnits() int } class Motorcycle { +getCapacityUnits() int } class ParkingSlot { -string slotId -int floorNumber -char sectionName -int slotNumber -vector~Vehicle*~ parkedVehicles +canFit(Vehicle*) bool +park(Vehicle*) bool +vacate(Vehicle*) bool +isEmpty() bool +getMotorcycleCount() int +hasCar() bool } class Section { -char name -vector~ParkingSlot*~ slots +getSlots() vector } class Floor { -int floorNumber -map~char, Section~ sections +getAllSlots() vector } class ParkingLot { -vector~Floor~ floors -map~string, ParkingSlot*~ slotLookup -map~string, ParkingTicket*~ activeTickets +park(Vehicle*) ParkingTicket* +unpark(ParkingTicket*, PaymentProcessor&) bool +calculateFare(ParkingTicket*) double +printLiveAvailability() void } class ParkingTicket { -string ticketId -Vehicle* vehicle -string allocatedSlotId -time_point entryTime -TicketStatus status } Vehicle <|-- Car Vehicle <|-- Motorcycle ParkingSlot "10" *-- Section Section "4" *-- Floor Floor "5" *-- ParkingLot ParkingLot ..> ParkingTicket

Step 4: Walking Through Core Methods in Plain Language

1. park(Vehicle* vehicle)

When a vehicle arrives at an entry gate: 1. Ask the SpotAllocator: "Given all 5 floors and this vehicle type, what is the best available slot?" - If vehicle is a MOTORCYCLE: * Check if any slot already has 1, 2, or 3 motorcycles (proximity order: Floor 1->5, Sec A->D, Slot 1->10). * If found, return that slot (packing heuristic). * If none found, find the closest completely EMPTY slot. - If vehicle is a CAR: * Find the closest completely EMPTY slot (0 bikes, 0 cars). 2. If no slot is returned -> REJECT: "Parking Lot Full for this vehicle type". 3. Call slot->park(vehicle): - Inserts vehicle pointer into slot's list after validating capacity. 4. Generate a unique ticket (e.g. "TKT-1001") with the current timestamp and slot ID. 5. Store ticket in activeTickets map and return it to the driver.

2. calculateFare(ParkingTicket* ticket)

When billing a parked vehicle: 1. Calculate the elapsed duration = current_time - ticket->entryTime. 2. Convert duration to total minutes. 3. If minutes <= 0 -> Charge minimum 1 hour (₹20). 4. Apply ceiling round-up: billableHours = (minutes + 59) / 60 fare = billableHours * 20.0 5. Return the calculated amount.

3. unpark(ParkingTicket* ticket, PaymentProcessor paymentMethod)

When a vehicle reaches the exit barrier: 1. Verify the ticket pointer is valid and has ACTIVE status. 2. Calculate the fare using calculateFare(). 3. Prompt payment through the provided payment method (UPI, Card, or Cash). 4. If payment fails -> REJECT: Keep exit barrier closed. 5. If payment succeeds: - Lookup the allocated slot from slotLookup map using ticket->allocatedSlotId. - Call slot->vacate(ticket->vehicle) to remove the vehicle. - Mark ticket status as PAID and set exitTime. - Remove ticket from activeTickets map. - Print exit receipt and open barrier.

Step 5: Explicit Edge Cases & How We Handle Them

In an SDE-1 interview at Juspay, this section is where you earn the highest marks. Always volunteer these scenarios without waiting for the interviewer to prompt you:

1. Slot-Sharing Overbooking (Enforcing 4-Bike Cap)

  • Problem: Attempting to park a 5th motorcycle into a slot that already has 4 motorcycles.
  • Our Solution: ParkingSlot::canFit() and ParkingSlot::park() explicitly check against MAX_BIKE_CAPACITY (4). When a slot reaches 4 bikes, park() returns false, and the allocator overflows to the next eligible slot.

2. Vehicle-Type Mismatch & Slot Corruption

  • Problem: A car arrives and attempts to take slot F1-A-S03 because the system saw "vacant capacity" (only 1 bike parked).
  • Our Solution: ParkingSlot::canFit() explicitly checks:
    if (vehicle->getType() == VehicleType::CAR) { return parkedVehicles.empty(); // MUST BE 100% EMPTY }
    Even if 3 bike spots are open, a car is strictly forbidden from entering.

3. Motorcycle Starvation of Cars

  • Problem: 10 motorcycles arrive and take 10 completely empty car slots, blocking 10 cars and leaving 30 unused motorcycle spots across those slots.
  • Our Solution: The MallOptimalSpotAllocator uses a two-tier search:
    • Tier 1: Search for slots already containing 1 to 3 bikes.
    • Tier 2: Only take a fresh empty slot if every existing motorcycle bay is 100% full (4/4).

4. Zero or Negligible Stay Duration

  • Problem: A driver enters by mistake and exits 2 minutes later.
  • Our Solution: Minimum billable duration is 1 hour (₹20). The calculation (minutes + 59) / 60 ensures any duration from 1 to 60 minutes bills exactly 1 hour.

5. Double Exit / Reused Ticket

  • Problem: A driver tries to exit twice with the same ticket or an already paid ticket.
  • Our Solution: activeTickets stores tickets with TicketStatus::ACTIVE. Once paid, the status transitions to PAID and it is evicted from activeTickets. Subsequent exit attempts immediately fail with "Invalid or already settled ticket".

6. Parking Lot Full

  • Problem: Every car slot is occupied, or all 5 floors are at capacity.
  • Our Solution: findBestSpot() returns nullptr. The system logs a graceful "āŒ [PARK REJECTED] No available spot found" message without crashing, throwing unhandled exceptions, or generating half-initialized tickets.

Step 6: Design Choices & Natural Justifications

[!IMPORTANT] Juspay Interview Tip: Do NOT say "I will now use the Strategy Pattern because GoF says so."
Instead, explain the practical engineering reason behind your structure. If the interviewer asks: "Is there a design pattern here?", you can naturally point it out and defend it.

1. Why decouple FeeCalculator into an interface?

  • Live Reasoning:
    "Instead of hardcoding an if-else block inside the ParkingLot class for billing, I extracted FeeCalculator into an abstract base class. Today the mall charges a flat ₹20/hr. Tomorrow, management might introduce weekend surge pricing (₹30/hr) or first-2-hours-free for moviegoers. By keeping fee calculation behind an interface, we can introduce new tariff logic without modifying a single line of the core checkout or parking lot code (Open-Closed Principle)."
    (Pattern Equivalent: Strategy Pattern)

2. Why decouple SpotAllocator into an interface?

  • Live Reasoning:
    "Different malls prioritize different parking layouts. One mall might want proximity-based allocation (closest to entrance), another might want distributed wear-and-tear (fill floor 2 before floor 1), and our mall requires motorcycle packing. Encapsulating this behind SpotAllocator lets us test, benchmark, or replace search heuristics independently of the physical floor hierarchy."
    (Pattern Equivalent: Strategy Pattern)

3. Why abstract PaymentProcessor?

  • Live Reasoning:
    "Exit kiosks need to accept UPI, Credit Cards, and Cash. Each payment gateway has a different third-party SDK and response signature. By defining an abstract PaymentProcessor with a clean processPayment(amount) contract, unpark() doesn't care whether the money came from a bank card or a cash box."
    (Pattern Equivalent: Strategy / Polymorphic Interface)

Step 7: Extensibility Without Breaking Existing Code

Here is how you explain adding future mall features during the wrap-up of the interview:

  1. Adding Electric Vehicle (EV) Charging Bays:

    • Add an isEvChargingAvailable boolean or an EV_CAR vehicle type.
    • ParkingSlot can have an isEvEquipped flag.
    • The SpotAllocator checks if (vehicle->needsCharging()) and filters for slots where slot->isEvEquipped() is true.
    • No changes needed to Floor, Section, or unpark().
  2. VIP / Reserved Handicap Slots:

    • Add a slot reservation tag (SlotCategory::VIP, SlotCategory::HANDICAPPED).
    • The allocator checks driver permissions before matching against reserved slots.
  3. Shopper Validation / Movie Theater Discount Passes:

    • Implement a new DiscountedFeeCalculator deriving from FeeCalculator.
    • Pass the cinema barcode during checkout to discount the calculated fare.

Step 8: Complete, Working C++ Real-World Simulation

Here is the clean, complete C++ implementation using standard, straightforward OOP:

#include <iostream> #include <string> #include <vector> #include <map> #include <chrono> #include <iomanip> #include <sstream> using namespace std; using namespace std::chrono; // ============================================================================ // 1. DOMAIN ENUMS & TYPES // ============================================================================ enum class VehicleType { CAR, MOTORCYCLE }; enum class TicketStatus { ACTIVE, PAID, CANCELLED }; string vehicleTypeToString(VehicleType type) { switch (type) { case VehicleType::CAR: return "CAR"; case VehicleType::MOTORCYCLE: return "MOTORCYCLE"; default: return "UNKNOWN"; } } // ============================================================================ // 2. VEHICLE CLASS HIERARCHY // ============================================================================ class Vehicle { protected: string licensePlate; VehicleType type; public: Vehicle(string plate, VehicleType t) { licensePlate = plate; type = t; } virtual ~Vehicle() = default; string getLicensePlate() const { return licensePlate; } VehicleType getType() const { return type; } virtual int getCapacityUnits() const = 0; }; class Car : public Vehicle { public: Car(string plate) : Vehicle(plate, VehicleType::CAR) {} int getCapacityUnits() const override { return 4; // Occupies full car slot (4 units) } }; class Motorcycle : public Vehicle { public: Motorcycle(string plate) : Vehicle(plate, VehicleType::MOTORCYCLE) {} int getCapacityUnits() const override { return 1; // 4 motorcycles fit in one standard car slot } }; // ============================================================================ // 3. PARKING SLOT (DUAL-CAPACITY BUSINESS LOGIC) // ============================================================================ class ParkingSlot { private: string slotId; int floorNumber; char sectionName; int slotNumber; const int MAX_BIKE_CAPACITY = 4; vector<Vehicle*> parkedVehicles; public: ParkingSlot(int floor, char section, int number) { floorNumber = floor; sectionName = section; slotNumber = number; ostringstream oss; oss << "F" << floor << "-" << section << "-S" << (number < 10 ? "0" : "") << number; slotId = oss.str(); } string getSlotId() const { return slotId; } int getFloorNumber() const { return floorNumber; } char getSectionName() const { return sectionName; } int getSlotNumber() const { return slotNumber; } bool canFit(Vehicle* vehicle) const { if (vehicle->getType() == VehicleType::CAR) { // A car can ONLY park if the slot is 100% empty return parkedVehicles.empty(); } else if (vehicle->getType() == VehicleType::MOTORCYCLE) { // Bike can park if no car is present and bike count < 4 if (!parkedVehicles.empty() && parkedVehicles[0]->getType() == VehicleType::CAR) { return false; } return parkedVehicles.size() < MAX_BIKE_CAPACITY; } return false; } bool park(Vehicle* vehicle) { if (vehicle->getType() == VehicleType::CAR) { if (!parkedVehicles.empty()) return false; parkedVehicles.push_back(vehicle); return true; } else if (vehicle->getType() == VehicleType::MOTORCYCLE) { if (!parkedVehicles.empty() && parkedVehicles[0]->getType() == VehicleType::CAR) { return false; } if (parkedVehicles.size() >= MAX_BIKE_CAPACITY) { return false; } parkedVehicles.push_back(vehicle); return true; } return false; } bool vacate(Vehicle* vehicle) { for (auto it = parkedVehicles.begin(); it != parkedVehicles.end(); ++it) { if ((*it)->getLicensePlate() == vehicle->getLicensePlate()) { parkedVehicles.erase(it); return true; } } return false; } bool isEmpty() const { return parkedVehicles.empty(); } bool hasCar() const { return (!parkedVehicles.empty() && parkedVehicles[0]->getType() == VehicleType::CAR); } int getMotorcycleCount() const { if (!parkedVehicles.empty() && parkedVehicles[0]->getType() == VehicleType::CAR) { return 0; } return (int)parkedVehicles.size(); } }; // ============================================================================ // 4. SECTION & FLOOR PHYSICAL HIERARCHY // ============================================================================ class Section { private: char name; vector<ParkingSlot*> slots; public: Section(char secName, int floorNumber, int slotCount) { name = secName; for (int i = 1; i <= slotCount; ++i) { slots.push_back(new ParkingSlot(floorNumber, secName, i)); } } char getName() const { return name; } vector<ParkingSlot*> getSlots() const { return slots; } }; class Floor { private: int floorNumber; map<char, Section> sections; public: Floor(int floorNum, vector<char> sectionNames, int slotsPerSection) { floorNumber = floorNum; for (char s : sectionNames) { sections.emplace(s, Section(s, floorNum, slotsPerSection)); } } int getFloorNumber() const { return floorNumber; } vector<ParkingSlot*> getAllSlots() const { vector<ParkingSlot*> allSlots; for (auto& pair : sections) { vector<ParkingSlot*> secSlots = pair.second.getSlots(); allSlots.insert(allSlots.end(), secSlots.begin(), secSlots.end()); } return allSlots; } }; // ============================================================================ // 5. SPOT ALLOCATOR (SMART PROXIMITY + BIKE PACKING HEURISTIC) // ============================================================================ class SpotAllocator { public: virtual ~SpotAllocator() = default; virtual ParkingSlot* findBestSpot( const vector<Floor>& floors, Vehicle* vehicle) = 0; }; class MallOptimalSpotAllocator : public SpotAllocator { public: ParkingSlot* findBestSpot( const vector<Floor>& floors, Vehicle* vehicle) override { // 1. For motorcycles, first prioritize packing into already partially occupied bike slots if (vehicle->getType() == VehicleType::MOTORCYCLE) { for (const auto& floor : floors) { for (auto slot : floor.getAllSlots()) { if (!slot->hasCar() && slot->getMotorcycleCount() > 0 && slot->getMotorcycleCount() < 4) { if (slot->canFit(vehicle)) { return slot; } } } } } // 2. If no partial bike slot exists (or vehicle is a Car), find closest completely empty slot for (const auto& floor : floors) { for (auto slot : floor.getAllSlots()) { if (slot->isEmpty()) { if (slot->canFit(vehicle)) { return slot; } } } } return nullptr; // Mall full } }; // ============================================================================ // 6. FEE CALCULATION ENGINE // ============================================================================ class FeeCalculator { public: virtual ~FeeCalculator() = default; virtual double calculateFee(long long minutesParked, VehicleType type) = 0; }; class MallHourlyFeeCalculator : public FeeCalculator { private: double hourlyRate; public: MallHourlyFeeCalculator(double rate = 20.0) { hourlyRate = rate; } double calculateFee(long long minutesParked, VehicleType) override { if (minutesParked <= 0) { return hourlyRate; // Minimum 1-hour charge } // Round up to nearest hour (ceiling calculation) long long hours = (minutesParked + 59) / 60; return hours * hourlyRate; } }; // ============================================================================ // 7. PAYMENT PROCESSOR CONTRACT & IMPLEMENTATIONS // ============================================================================ class PaymentProcessor { public: virtual ~PaymentProcessor() = default; virtual bool processPayment(double amount) = 0; virtual string getModeName() const = 0; }; class UPIPaymentProcessor : public PaymentProcessor { private: string vpa; public: UPIPaymentProcessor(string upiId) { vpa = upiId; } bool processPayment(double amount) override { cout << " šŸ’³ [UPI Gateway] Deducted ₹" << fixed << setprecision(2) << amount << " from VPA " << vpa << ". [SUCCESS]\n"; return true; } string getModeName() const override { return "UPI (" + vpa + ")"; } }; class CardPaymentProcessor : public PaymentProcessor { private: string maskedCard; public: CardPaymentProcessor(string cardNumber) { if (cardNumber.length() > 4) { maskedCard = "XXXX-XXXX-XXXX-" + cardNumber.substr(cardNumber.length() - 4); } else { maskedCard = cardNumber; } } bool processPayment(double amount) override { cout << " šŸ’³ [Card Gateway] Charged ₹" << fixed << setprecision(2) << amount << " to Card " << maskedCard << ". [SUCCESS]\n"; return true; } string getModeName() const override { return "CARD (" + maskedCard + ")"; } }; class CashPaymentProcessor : public PaymentProcessor { public: bool processPayment(double amount) override { cout << " šŸ’µ [Cash Counter] Received ₹" << fixed << setprecision(2) << amount << " in cash. Receipt printed. [SUCCESS]\n"; return true; } string getModeName() const override { return "CASH"; } }; // ============================================================================ // 8. PARKING TICKET // ============================================================================ class ParkingTicket { private: string ticketId; Vehicle* vehicle; string allocatedSlotId; system_clock::time_point entryTime; system_clock::time_point exitTime; double feeCharged = 0.0; TicketStatus status; public: ParkingTicket(string id, Vehicle* v, string slotId, system_clock::time_point entry) { ticketId = id; vehicle = v; allocatedSlotId = slotId; entryTime = entry; status = TicketStatus::ACTIVE; } string getTicketId() const { return ticketId; } Vehicle* getVehicle() const { return vehicle; } string getAllocatedSlotId() const { return allocatedSlotId; } system_clock::time_point getEntryTime() const { return entryTime; } TicketStatus getStatus() const { return status; } double getFeeCharged() const { return feeCharged; } void markPaid(system_clock::time_point exit, double fee) { exitTime = exit; feeCharged = fee; status = TicketStatus::PAID; } }; // ============================================================================ // 9. PARKING LOT SYSTEM ORCHESTRATOR // ============================================================================ class ParkingLot { private: string name; vector<Floor> floors; map<string, ParkingSlot*> slotLookup; map<string, ParkingTicket*> activeTickets; SpotAllocator* allocator; FeeCalculator* feeCalculator; int ticketCounter = 1000; public: ParkingLot(string lotName, int totalFloors, vector<char> sections, int slotsPerSection) { name = lotName; allocator = new MallOptimalSpotAllocator(); feeCalculator = new MallHourlyFeeCalculator(20.0); // ₹20/hr for (int f = 1; f <= totalFloors; ++f) { floors.push_back(Floor(f, sections, slotsPerSection)); } // Build index of all slots for O(1) unparking lookup for (const auto& floor : floors) { for (auto slot : floor.getAllSlots()) { slotLookup[slot->getSlotId()] = slot; } } } ~ParkingLot() { delete allocator; delete feeCalculator; } ParkingTicket* park(Vehicle* vehicle, system_clock::time_point entryTime) { ParkingSlot* bestSpot = allocator->findBestSpot(floors, vehicle); if (!bestSpot) { cout << "āŒ [PARK REJECTED] No available spot found for " << vehicleTypeToString(vehicle->getType()) << " [" << vehicle->getLicensePlate() << "]!\n"; return nullptr; } if (!bestSpot->park(vehicle)) { cout << "āŒ [PARK CONFLICT] Failed to reserve spot " << bestSpot->getSlotId() << ".\n"; return nullptr; } string ticketId = "TKT-" + to_string(++ticketCounter); ParkingTicket* ticket = new ParkingTicket(ticketId, vehicle, bestSpot->getSlotId(), entryTime); activeTickets[ticketId] = ticket; cout << "āœ… [PARK SUCCESS] " << vehicleTypeToString(vehicle->getType()) << " [" << vehicle->getLicensePlate() << "] parked at " << bestSpot->getSlotId() << " | Ticket: " << ticketId << "\n"; return ticket; } double calculateFare(ParkingTicket* ticket, system_clock::time_point currentTime) { auto duration = duration_cast<minutes>(currentTime - ticket->getEntryTime()).count(); return feeCalculator->calculateFee(duration, ticket->getVehicle()->getType()); } bool unpark(ParkingTicket* ticket, PaymentProcessor& paymentMethod, system_clock::time_point exitTime) { if (!ticket || ticket->getStatus() != TicketStatus::ACTIVE) { cout << "āŒ [UNPARK FAILED] Invalid or already settled ticket.\n"; return false; } auto duration = duration_cast<minutes>(exitTime - ticket->getEntryTime()).count(); double fare = feeCalculator->calculateFee(duration, ticket->getVehicle()->getType()); cout << "\nšŸ [CHECKOUT INITIATED] Vehicle " << ticket->getVehicle()->getLicensePlate() << " | Slot: " << ticket->getAllocatedSlotId() << " | Duration: " << duration << " mins | Amount: ₹" << fixed << setprecision(2) << fare << "\n"; if (!paymentMethod.processPayment(fare)) { cout << "āŒ [PAYMENT FAILED] Barrier remains closed.\n"; return false; } auto it = slotLookup.find(ticket->getAllocatedSlotId()); if (it != slotLookup.end()) { it->second->vacate(ticket->getVehicle()); } ticket->markPaid(exitTime, fare); activeTickets.erase(ticket->getTicketId()); cout << "šŸ”“ [EXIT PERMITTED] Paid via " << paymentMethod.getModeName() << ". Bay " << ticket->getAllocatedSlotId() << " released. Safe journey!\n"; return true; } void printLiveAvailability() const { cout << "\n==========================================================================\n"; cout << "šŸ“Š LIVE MALL PARKING DISPLAY: " << name << "\n"; cout << "==========================================================================\n"; for (const auto& floor : floors) { int emptySlots = 0; int carsParked = 0; int partialBikeSlots = 0; int fullBikeSlots = 0; for (auto slot : floor.getAllSlots()) { if (slot->isEmpty()) { emptySlots++; } else if (slot->hasCar()) { carsParked++; } else if (slot->getMotorcycleCount() == 4) { fullBikeSlots++; } else { partialBikeSlots++; } } cout << "Floor " << floor.getFloorNumber() << " -> Empty Slots: " << setw(2) << emptySlots << " | Cars: " << setw(2) << carsParked << " | Partial Bike Bays: " << setw(2) << partialBikeSlots << " | Full Bike Bays: " << setw(2) << fullBikeSlots << "\n"; } cout << "==========================================================================\n\n"; } }; // ============================================================================ // 10. REAL-WORLD SIMULATION (DEMONSTRATING ALL CASES) // ============================================================================ int main() { cout << "==================================================================\n"; cout << "šŸš— 5-FLOOR SHOPPING MALL PARKING LOT SYSTEM - REAL-WORLD SIMULATION\n"; cout << "==================================================================\n"; // Setup: 5 floors, sections A, B, C, D, 10 slots each (200 slots total) vector<char> sections = {'A', 'B', 'C', 'D'}; ParkingLot mallParking("Grand Orion Mall - Express Parking", 5, sections, 10); mallParking.printLiveAvailability(); auto baseTime = system_clock::now(); // ------------------------------------------------------------- // SCENARIO 1: Park Cars (Must take fresh empty slots in proximity order) // ------------------------------------------------------------- cout << "--- 1. PARKING CARS ---\n"; Vehicle* car1 = new Car("KA-01-AB-1234"); Vehicle* car2 = new Car("KA-03-CD-5678"); ParkingTicket* tktCar1 = mallParking.park(car1, baseTime); ParkingTicket* tktCar2 = mallParking.park(car2, baseTime + minutes(10)); // ------------------------------------------------------------- // SCENARIO 2: Motorcycle Packing Test (4 bikes share 1 car slot) // All 4 bikes should pack into the SAME slot (F1-A-S03) // ------------------------------------------------------------- cout << "\n--- 2. PARKING 4 MOTORCYCLES (SHOULD ALL PACK INTO F1-A-S03) ---\n"; Vehicle* bike1 = new Motorcycle("KA-04-E-1111"); Vehicle* bike2 = new Motorcycle("KA-04-E-2222"); Vehicle* bike3 = new Motorcycle("KA-04-E-3333"); Vehicle* bike4 = new Motorcycle("KA-04-E-4444"); ParkingTicket* tktBike1 = mallParking.park(bike1, baseTime + minutes(15)); ParkingTicket* tktBike2 = mallParking.park(bike2, baseTime + minutes(16)); ParkingTicket* tktBike3 = mallParking.park(bike3, baseTime + minutes(18)); ParkingTicket* tktBike4 = mallParking.park(bike4, baseTime + minutes(20)); cout << "\nšŸ” PACKING VERIFICATION:\n"; cout << " Bike 1 Spot: " << tktBike1->getAllocatedSlotId() << "\n"; cout << " Bike 2 Spot: " << tktBike2->getAllocatedSlotId() << "\n"; cout << " Bike 3 Spot: " << tktBike3->getAllocatedSlotId() << "\n"; cout << " Bike 4 Spot: " << tktBike4->getAllocatedSlotId() << "\n"; // ------------------------------------------------------------- // SCENARIO 3: 5th Motorcycle Arrives (Bay F1-A-S03 is full -> Takes F1-A-S04) // ------------------------------------------------------------- cout << "\n--- 3. 5TH MOTORCYCLE ARRIVES (SLOT FULL -> TAKES F1-A-S04) ---\n"; Vehicle* bike5 = new Motorcycle("KA-05-ZZ-9999"); ParkingTicket* tktBike5 = mallParking.park(bike5, baseTime + minutes(25)); // ------------------------------------------------------------- // SCENARIO 4: New Car Arrives (Must NOT park in F1-A-S04 with Bike 5; Takes F1-A-S05) // ------------------------------------------------------------- cout << "\n--- 4. NEW CAR ARRIVES (SKIPS PARTIAL BIKE SLOT F1-A-S04 -> TAKES F1-A-S05) ---\n"; Vehicle* car3 = new Car("KA-02-MM-9000"); ParkingTicket* tktCar3 = mallParking.park(car3, baseTime + minutes(30)); mallParking.printLiveAvailability(); // ------------------------------------------------------------- // SCENARIO 5: Exit & Fare Calculation (₹20 per hour) // ------------------------------------------------------------- cout << "--- 5. UNPARKING & BILLING ---\n"; // Car 1 parked for 165 minutes (2h 45m) -> Ceiling of 3 hours @ ₹20 = ₹60 UPIPaymentProcessor upiGateway("alex@oksbi"); mallParking.unpark(tktCar1, upiGateway, baseTime + minutes(165)); // Bike 1 parked for 45 minutes -> Minimum 1 hour @ ₹20 = ₹20 CashPaymentProcessor cashCounter; mallParking.unpark(tktBike1, cashCounter, baseTime + minutes(45)); // ------------------------------------------------------------- // SCENARIO 6: Re-packing Test // Slot F1-A-S03 now has 3 bikes (1 vacated). Next bike should fill that spot! // ------------------------------------------------------------- cout << "\n--- 6. RE-PACKING TEST: BIKE 6 ARRIVES (SHOULD FILL VACATED BAY IN F1-A-S03) ---\n"; Vehicle* bike6 = new Motorcycle("KA-51-EX-7777"); ParkingTicket* tktBike6 = mallParking.park(bike6, baseTime + minutes(50)); cout << "šŸ” RE-PACK VERIFICATION: Bike 6 parked at " << tktBike6->getAllocatedSlotId() << " (Expected: F1-A-S03)\n"; mallParking.printLiveAvailability(); return 0; }

šŸ–„ļø Expected Simulation Output Walkthrough

================================================================== šŸš— 5-FLOOR SHOPPING MALL PARKING LOT SYSTEM - REAL-WORLD SIMULATION ================================================================== ========================================================================== šŸ“Š LIVE MALL PARKING DISPLAY: Grand Orion Mall - Express Parking ========================================================================== Floor 1 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 2 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 3 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 4 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 5 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 ========================================================================== --- 1. PARKING CARS --- āœ… [PARK SUCCESS] CAR [KA-01-AB-1234] parked at F1-A-S01 | Ticket: TKT-1001 āœ… [PARK SUCCESS] CAR [KA-03-CD-5678] parked at F1-A-S02 | Ticket: TKT-1002 --- 2. PARKING 4 MOTORCYCLES (SHOULD ALL PACK INTO F1-A-S03) --- āœ… [PARK SUCCESS] MOTORCYCLE [KA-04-E-1111] parked at F1-A-S03 | Ticket: TKT-1003 āœ… [PARK SUCCESS] MOTORCYCLE [KA-04-E-2222] parked at F1-A-S03 | Ticket: TKT-1004 āœ… [PARK SUCCESS] MOTORCYCLE [KA-04-E-3333] parked at F1-A-S03 | Ticket: TKT-1005 āœ… [PARK SUCCESS] MOTORCYCLE [KA-04-E-4444] parked at F1-A-S03 | Ticket: TKT-1006 šŸ” PACKING VERIFICATION: Bike 1 Spot: F1-A-S03 Bike 2 Spot: F1-A-S03 Bike 3 Spot: F1-A-S03 Bike 4 Spot: F1-A-S03 --- 3. 5TH MOTORCYCLE ARRIVES (SLOT FULL -> TAKES F1-A-S04) --- āœ… [PARK SUCCESS] MOTORCYCLE [KA-05-ZZ-9999] parked at F1-A-S04 | Ticket: TKT-1007 --- 4. NEW CAR ARRIVES (SKIPS PARTIAL BIKE SLOT F1-A-S04 -> TAKES F1-A-S05) --- āœ… [PARK SUCCESS] CAR [KA-02-MM-9000] parked at F1-A-S05 | Ticket: TKT-1008 ========================================================================== šŸ“Š LIVE MALL PARKING DISPLAY: Grand Orion Mall - Express Parking ========================================================================== Floor 1 -> Empty Slots: 35 | Cars: 3 | Partial Bike Bays: 1 | Full Bike Bays: 1 Floor 2 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 3 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 4 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 5 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 ========================================================================== --- 5. UNPARKING & BILLING --- šŸ [CHECKOUT INITIATED] Vehicle KA-01-AB-1234 | Slot: F1-A-S01 | Duration: 165 mins | Amount: ₹60.00 šŸ’³ [UPI Gateway] Deducted ₹60.00 from VPA alex@oksbi. [SUCCESS] šŸ”“ [EXIT PERMITTED] Paid via UPI (alex@oksbi). Bay F1-A-S01 released. Safe journey! šŸ [CHECKOUT INITIATED] Vehicle KA-04-E-1111 | Slot: F1-A-S03 | Duration: 45 mins | Amount: ₹20.00 šŸ’µ [Cash Counter] Received ₹20.00 in cash. Receipt printed. [SUCCESS] šŸ”“ [EXIT PERMITTED] Paid via CASH. Bay F1-A-S03 released. Safe journey! --- 6. RE-PACKING TEST: BIKE 6 ARRIVES (SHOULD FILL VACATED BAY IN F1-A-S03) --- āœ… [PARK SUCCESS] MOTORCYCLE [KA-51-EX-7777] parked at F1-A-S03 | Ticket: TKT-1009 šŸ” RE-PACK VERIFICATION: Bike 6 parked at F1-A-S03 (Expected: F1-A-S03) ========================================================================== šŸ“Š LIVE MALL PARKING DISPLAY: Grand Orion Mall - Express Parking ========================================================================== Floor 1 -> Empty Slots: 36 | Cars: 2 | Partial Bike Bays: 1 | Full Bike Bays: 1 Floor 2 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 3 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 4 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 Floor 5 -> Empty Slots: 40 | Cars: 0 | Partial Bike Bays: 0 | Full Bike Bays: 0 ==========================================================================

šŸŽÆ Quick Cheat-Sheet for the Juspay Round

When wrapping up your solution with the interviewer, summarize with this 30-second closing pitch:

  1. Clean Real-World Modeling: "The system mirrors the exact physical structure of the mall: ParkingLot $\to$ Floor $\to$ Section $\to$ ParkingSlot."
  2. Strict Dual-Capacity Invariants: "A slot holds 4 capacity units. A car consumes 4 units only if empty. A bike consumes 1 unit up to 4 bikes. No car can ever park in a slot containing a motorcycle."
  3. Smart Heuristic: "We optimize mall revenue by packing motorcycles together first, keeping empty bays preserved for cars."
  4. Robust Domain Validation: "We strictly enforce the entry $\to$ ticket $\to$ fare $\to$ payment $\to$ exit lifecycle with graceful capacity validation."