š§ 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:
- How you clarify ambiguous constraints before writing a single line of code.
- How intuitively your classes model real-world physical objects (floors, sections, slots, cars, bikes).
- How rigorously you think through edge cases (e.g., dual-capacity slot sharing, mall full, vehicle mismatch, payment failures).
- How you explain logic out loud in plain English before coding methods like
park()orunpark(). - 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:
- Mall Physical Layout: How many floors are there, how are they subdivided, and what is the total capacity?
- Vehicle Types & Slot Sharing: What vehicles are permitted, and can smaller vehicles share larger bays?
- Entry & Spot Selection: Does the system suggest the best/nearest spot, or can the driver park anywhere?
- Tariff & Exit Billing: What is the pricing model, how do we treat partial hours, and what payment modes must be supported?
- 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 ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā-
Hierarchy & Dimensions:
- 5 Floors (1 to 5).
- Each floor has 4 Sections:
A,B,C,D. - Each section has 10 Car Slots (Numbered
1to10). - Total base slots across the entire mall = $5 \times 4 \times 10 = 200\text{ car slots}$.
-
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.
-
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!
- Base heuristic: Proximity to entrance $\to$ Lowest floor first (
-
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.
-
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(VehicleTypeenum: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(TicketStatusenum: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 fromslotIdto 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
-
Composition (Has-A / Tree Hierarchy):
ParkingLotcontains 5Floorobjects.- Each
Floorcontains 4Sectionobjects (A,B,C,D). - Each
Sectioncontains 10ParkingSlotpointers. - Each
ParkingSlotholds 0 to 4Vehiclepointers. ParkingLotmaintains activeParkingTicketpointers.
-
Inheritance (Is-A):
Caris-aVehicle.Motorcycleis-aVehicle.
-
Abstract Contracts / Extensibility Points (Polymorphism):
SpotAllocator(Interface) $\to$ implemented byMallOptimalSpotAllocator.FeeCalculator(Interface) $\to$ implemented byMallHourlyFeeCalculator.PaymentProcessor(Interface) $\to$ implemented byUPIPaymentProcessor,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 ..> ParkingTicketStep 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()andParkingSlot::park()explicitly check againstMAX_BIKE_CAPACITY(4). When a slot reaches 4 bikes,park()returnsfalse, 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-S03because the system saw "vacant capacity" (only 1 bike parked). - Our Solution:
ParkingSlot::canFit()explicitly checks:Even if 3 bike spots are open, a car is strictly forbidden from entering.if (vehicle->getType() == VehicleType::CAR) { return parkedVehicles.empty(); // MUST BE 100% EMPTY }
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
MallOptimalSpotAllocatoruses 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) / 60ensures 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:
activeTicketsstores tickets withTicketStatus::ACTIVE. Once paid, the status transitions toPAIDand it is evicted fromactiveTickets. 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()returnsnullptr. 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 anif-elseblock inside theParkingLotclass for billing, I extractedFeeCalculatorinto 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 behindSpotAllocatorlets 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 abstractPaymentProcessorwith a cleanprocessPayment(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:
-
Adding Electric Vehicle (EV) Charging Bays:
- Add an
isEvChargingAvailableboolean or anEV_CARvehicle type. ParkingSlotcan have anisEvEquippedflag.- The
SpotAllocatorchecksif (vehicle->needsCharging())and filters for slots whereslot->isEvEquipped()is true. - No changes needed to
Floor,Section, orunpark().
- Add an
-
VIP / Reserved Handicap Slots:
- Add a slot reservation tag (
SlotCategory::VIP,SlotCategory::HANDICAPPED). - The allocator checks driver permissions before matching against reserved slots.
- Add a slot reservation tag (
-
Shopper Validation / Movie Theater Discount Passes:
- Implement a new
DiscountedFeeCalculatorderiving fromFeeCalculator. - Pass the cinema barcode during checkout to discount the calculated fare.
- Implement a new
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:
- Clean Real-World Modeling: "The system mirrors the exact physical structure of the mall:
ParkingLot$\to$Floor$\to$Section$\to$ParkingSlot." - 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."
- Smart Heuristic: "We optimize mall revenue by packing motorcycles together first, keeping empty bays preserved for cars."
- Robust Domain Validation: "We strictly enforce the entry $\to$ ticket $\to$ fare $\to$ payment $\to$ exit lifecycle with graceful capacity validation."