Sitemap

A Beginner’s Guide to Designing a Parking Lot System

7 min readJul 3, 2026

--

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 <|-- UPIPayment
PricingStrategy <|-- NormalPricing
PricingStrategy <|-- WeekendPricing

Responsibilities 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 Payment
ParkingSpot
│
├── Knows whether it is occupied
├── Parks a vehicle
└── Removes a vehicle
Vehicle
│
└── Stores vehicle information
Ticket
│
├── Stores entry time
├── Stores exit time
├── Knows the parked vehicle
├── Knows the allotted parking spot
└── Calculates parking duration
Payment
│
└── Processes payment
├── CashPayment
└── UPIPayment
PricingStrategy
└── Calculates parking fee
├── NormalPricingStrategy
└── WeekendPricingStrategy

UML Class Diagram

Press enter or click to view image in full size

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 Ticket

removeVehicle()

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

PricingStrategy

Reason:

Pricing algorithm can change without modifying ParkingLot.

Runtime Polymorphism

Used in

Payment

ParkingLot simply calls

payment->makePayment()

without caring

Cash

UPI

Card

Wallet

etc.

Composition

ParkingLot
↓ParkingSpot

ParkingLot owns ParkingSpots.

SOLID Principles Used

Single Responsibility

Vehicle
ParkingSpotTicket

Each has one responsibility.

Open Closed Principle

Adding

CardPayment

requires

No changes
inside ParkingLot.

Similarly

Adding

HolidayPricing

doesn’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.

--

--