A Beginner’s Guide to Designing a Parking Lot System
Problem Statement
Design a Parking Lot System that supports:
- Parking different types of vehicles.
- Removing vehicles.
- Generating tickets.
- Calculating parking duration.
- Different payment methods.
- Different pricing strategies.
Understanding the Real World
A vehicle enters the parking lot → Someone (or a system) checks if an appropriate parking spot is available → If a spot is available, the vehicle is assigned that parking spot→A parking ticket is generated → The vehicle remains parked → When the vehicle exits, the ticket is presented → The system calculates the parking duration → Based on the duration and vehicle type, the parking fee is calculated → The user pays using a payment method (Cash, UPI, etc.) → After successful payment, the parking spot becomes available again.
Functional Requirements
- Park Vehicle
- isSpotAvailable
- Generate Ticket
- Calculate Parking Duration
- Remove Vehicle
- Calculate Parking Fee
- Accept Different Payment Modes
Supported Vehicle Types
- Car
- Bike
- Truck
Supported Payment Modes
- Cash
- UPI
Supported Pricing
- Normal Pricing
- Weekend Pricing
What did I learn from this?
While visualizing the real-world process, I noticed the important entities involved.
Real World Object Possible Class Parking Lot ParkingLot Parking Space ParkingSpot Vehicle Vehicle Ticket Ticket Payment Counter Payment Pricing Rules PricingStrategy
This naturally led me to identify the classes for the system.
Here’s a design tip
Not every word becomes a class.
Ask yourself these questions:
Does it have state?
Does it have behavior?
Does it have its own lifecycle?
If the answer is mostly “yes,” it’s often a good candidate for a class.
Non Functional Requirements
- Extensible
- Maintainable
- Scalable
- Easy to add new payment methods
- Easy to add new pricing algorithms
- O(1) Ticket lookup
- O(n) Spot lookup
Responsibility Assignment
One of the biggest lessons in LLD:
Every class should have one clear responsibility — This is called Single Responsibility Principle
Class Responsibility Vehicle Stores vehicle information ParkingSpot Represents one parking space Ticket Represents one parking session Payment Handles payment PricingStrategy Calculates parking fee ParkingLot Coordinates everything — Orchestrator
How I Thought
Real World → Vehicle → Where does it park → Parking Spot → How do I remember parking? → Ticket → Who controls everything? → Parking Lot → How is fee calculated? → Pricing Strategy → How is payment done? → Payment
Relationship Between Classes
ParkingLot
│
├── owns many ParkingSpot (Composition) [Parking Spots cannot exist without Parking Lot]
│
└── owns many active Ticket (Composition)
ParkingSpot
│
├── refers to one Vehicle (Association) [Objects are connected, but they can exist independently]
│
└── has one VehicleType (Attribute)
Ticket
│
├── refers to one Vehicle (Association)
│
└── refers to one ParkingSpot (Association)
Payment
│
├── CashPayment (Inheritance)
│
└── UPIPayment (Inheritance)
PricingStrategy
│
├── NormalPricingStrategy (Inheritance)
│
└── WeekendPricingStrategy (Inheritance)
+----------------+
| ParkingLot |
+----------------+
| owns
|
-------------------------
| |
+----------------+ +----------------+
| ParkingSpot | | Ticket |
+----------------+ +----------------+
| |
| |
+----------+ +-------------+
| Vehicle |<---------| Vehicle* |
+----------+ | Spot* │
+-------------+
Payment <|-- CashPayment
Payment <|-- UPIPaymentPricingStrategy <|-- NormalPricing
PricingStrategy <|-- WeekendPricingResponsibilities of Each Class
ParkingLot
│
├── Creates parking spots
├── Finds an available parking spot
├── Parks a vehicle
├── Creates a parking ticket
├── Stores active tickets
├── Removes a vehicle
├── Calculates parking fee using PricingStrategy
└── Processes payment using PaymentParkingSpot
│
├── Knows whether it is occupied
├── Parks a vehicle
└── Removes a vehicleVehicle
│
└── Stores vehicle informationTicket
│
├── Stores entry time
├── Stores exit time
├── Knows the parked vehicle
├── Knows the allotted parking spot
└── Calculates parking durationPayment
│
└── Processes payment
├── CashPayment
└── UPIPaymentPricingStrategy
└── Calculates parking fee
├── NormalPricingStrategy
└── WeekendPricingStrategy
UML Class Diagram
C++ Code
Firstly vehicle will arrive for Parking so let’s first create the Vehicle Class
enum class VehicleType {
Car,
Bike,
Truck
};//Single Responsibility Principle satisfied here.
class Vehicle {
private:
string registrationNumber;
VehicleType type; public:
Vehicle(string registrationNumber, VehicleType type) {
this->registrationNumber = registrationNumber;
this->type = type;
} VehicleType getType() {
return type;
} string getRegistrationNumber() {
return registrationNumber;
}};
Now let’s next think of Parking Spot
class ParkingSpot {
private:
int spotId;
Vehicle* parkedVehicle;
VehicleType spotType;public:
ParkingSpot(int spotId, VehicleType spotType){
this->spotId = spotId;
this->spotType = spotType;
this->parkedVehicle = NULL;
}
bool isAvailable() {
if(parkedVehicle == NULL) {
return true;
}
return false;
} bool parkVehicle(Vehicle* vehicle){
if(isAvailable() && vehicle->getType() == spotType) {
parkedVehicle = vehicle;
return true;
}
return false;
} bool removeVehicle() {
if(!isAvailable()) {
parkedVehicle = NULL;
return true;
}
return false;
} //Let's create getter as Ticket class might need to know the spotId
int getSpotId() {
return spotId;
} VehicleType getSpotType() {
return spotType;
}
};
Next we will think of Ticket class
class Ticket {
private:
int ticketId;
ParkingSpot* spot;
time_t startTime;
time_t endTime;
Vehicle* vehicle;public:
Ticket(int ticketId, ParkingSpot* spot, Vehicle* vehicle, time_t startTime) {
this->ticketId = ticketId;
this->spot = spot;
this->vehicle = vehicle;
this->startTime = startTime;
this->endTime = 0; // 0 indicates parking is still ongoing
} int getTicketId() {
return ticketId;
}
//The object that owns the data should usually own the behavior on that data.
int getParkingDurationInSeconds() {
if(endTime == 0) {
return -1; // Parking is still ongoing
}
return (endTime - startTime);
} void closeTicket(time_t exitTime) {
this->endTime = exitTime;
} ParkingSpot* getSpot() {
return spot;
} Vehicle* getVehicle() {
return vehicle;
}
};
Now is the time for Payment class
class Payment {
public:
virtual bool makePayment(double amount) = 0; //pure virtual function
};class CashPayment : public Payment {
public:
bool makePayment(double amount) override{
cout << "Processing cash payment of $" << amount << endl;
return true; // Assume payment is always successful for simplicity
}
};class UPIPayment : public Payment {
public:
bool makePayment(double amount) override {
cout << "Processing UPI payment of $" << amount << endl;
return true; // Assume payment is always successful for simplicity
}
};
Now we will se Pricing Strategy and with that let’s understand Template Method
class PricingStrategy {
public:double calculateFee(Ticket* ticket) {
int duration = ticket->getParkingDurationInSeconds();
VehicleType type = ticket->getVehicle()->getType(); double rate = getRatePerHour(type); return (duration / 3600.0) * rate;
} virtual double getRatePerHour(VehicleType type) = 0;
};//Concrete implementation of PricingStrategy
class NormalPricingStrategy : public PricingStrategy {
public:
double getRatePerHour(VehicleType type) override {
switch(type) {
case VehicleType::Car:
return 2.0; // $2 per hour
case VehicleType::Bike:
return 1.0; // $1 per hour
case VehicleType::Truck:
return 3.0; // $3 per hour
default:
return 0.0;
}
}
};class WeekendPricingStrategy : public PricingStrategy {
public:
double getRatePerHour(VehicleType type) override {
switch(type) {
case VehicleType::Car:
return 3.0;
case VehicleType::Bike:
return 1.5;
case VehicleType::Truck:
return 4.0;
default:
return 0.0;
}
}
};
Template method has been used here -
Get Deeksha Sharma’s stories in your inbox
Join Medium for free to get updates from this writer.
The parent says:
“I know the steps.”
The child says:
“I’ll provide one piece of the puzzle.”
The parent controls the algorithm.
The child customizes one step.
Step 1
Compiler enters
PricingStrategy::calculateFee()
because this function isn’t virtual.
Step 2
Inside it
double rate = getRatePerHour(type);
Now getRatePerHour() is virtual.
The actual object is
NormalPricingStrategy
So C++ says:
“Oh! I’ll call the child version.” Therefore
NormalPricingStrategy::getRatePerHour() runs.
Lastly the orchestrator that is Parking Lot but let’s first understand park and remove vehicle flow
parkVehicle()
Vehicle arrives
↓
findParkingSpot()
↓
spot->parkVehicle()
↓
create Ticket
↓
store Ticket
↓
return TicketremoveVehicle()
ticketId received
↓
find Ticket
↓
close Ticket
↓
spot->removeVehicle()
↓
calculate fee
↓
payment
↓
erase active ticket//Orchestrating Class
class ParkingLot {
private:
vector<ParkingSpot*> spots;
unordered_map<int, Ticket*> activeTickets; // Maps ticketId to Ticket
int nextTicketId;ParkingSpot* findParkingSpot(Vehicle* vehicle) {
for(auto spot : spots) {
if(spot->isAvailable() && spot->getSpotType() == vehicle->getType()) {
return spot;
}
}
return NULL; // No available spot found
} public:
ParkingLot(int numCarSpots, int numBikeSpots, int numTruckSpots) {
nextTicketId = 1;
int spotId = 1;
for(int i = 0; i < numCarSpots; i++) {
spots.push_back(new ParkingSpot(spotId++, VehicleType::Car));
}
for(int i = 0; i < numBikeSpots; i++) {
spots.push_back(new ParkingSpot(spotId++, VehicleType::Bike));
}
for(int i = 0; i < numTruckSpots; i++) {
spots.push_back(new ParkingSpot(spotId++, VehicleType::Truck));
}
} Ticket* parkVehicle(Vehicle* vehicle){
ParkingSpot* spot = findParkingSpot(vehicle);
if(spot != NULL) {
spot->parkVehicle(vehicle);
time_t now = time(0);
Ticket* ticket = new Ticket(nextTicketId++, spot, vehicle, now);
activeTickets[ticket->getTicketId()] = ticket;
cout << "Vehicle parked successfully on Spot "<< spot->getSpotId() << endl;
return ticket;
}
cout << "No available parking spot." << endl;
return NULL;
} void removeVehicle(int ticketId, Payment* paymentMethod, PricingStrategy* pricingStrategy) {
//Now we will check if this ticket is there
if(activeTickets.find(ticketId) != activeTickets.end()) {
Ticket* ticket = activeTickets[ticketId];
time_t now = time(0);
ticket->closeTicket(now);
double amountDue = pricingStrategy->calculateFee(ticket);
cout << "Amount due for ticket " << ticketId << ": $" << amountDue << endl; if(paymentMethod->makePayment(amountDue)) {
ticket->getSpot()->removeVehicle();
activeTickets.erase(ticketId);
delete ticket; // Clean up the ticket
} else {
cout << "Payment failed." << endl;
}
} else {
cout << "Invalid ticket ID." << endl;
}
}
};
Design Patterns Used
Strategy Pattern
Used for
PricingStrategyReason:
Pricing algorithm can change without modifying ParkingLot.
Runtime Polymorphism
Used in
PaymentParkingLot simply calls
payment->makePayment()without caring
Cash
UPI
Card
Wallet
etc.
Composition
ParkingLot↓ParkingSpot
ParkingLot owns ParkingSpots.
SOLID Principles Used
Single Responsibility
VehicleParkingSpotTicket
Each has one responsibility.
Open Closed Principle
Adding
CardPaymentrequires
No changesinside ParkingLot.Similarly
Adding
HolidayPricingdoesn’t modify ParkingLot.
Liskov Substitution
ParkingLot can use
Payment*regardless of whether object is
Cash
UPI
Card
etc.
Dependency Inversion
ParkingLot depends on abstraction (Payment, PricingStrategy) instead of CashPayment, UPIPayment, WeekendPricing
In Low-Level Design, focus on assigning the right responsibility to the right class. Code becomes much easier once responsibilities are clear.









