💡 ParkingLot aggregates ParkingSpot objects and maintains availability count.
fill_row
Add method to add spots to ParkingLot
We add a method to ParkingLot to add new ParkingSpot instances and update availability.
💡 This method allows dynamic configuration of the parking lot size and spot types.
Line:def add_parking_spot(self, spot):
self.spots.append(spot)
if not spot.occupied:
self.available_spots += 1
💡 Adding spots updates the internal state and availability count.
compare
Implement method to find suitable spot for a vehicle
We implement a method in ParkingLot to find the first available spot that can fit the vehicle size.
💡 This method is key to parking logic, matching vehicle size to spot size.
Line:def find_spot_for_vehicle(self, vehicle):
for spot in self.spots:
if not spot.occupied and spot.size >= vehicle.get_size():
return spot
return None
💡 This step demonstrates searching and conditional logic to allocate parking efficiently.
insert
Implement park_vehicle method
We implement the park_vehicle method that uses find_spot_for_vehicle to assign a vehicle to a spot and update occupancy.
💡 This method encapsulates the core parking operation, combining search and state update.
💡 Extensibility is achieved via inheritance and polymorphism.
reconstruct
Final state: Complete Parking Lot design
The final design shows all classes, their relationships, and key methods supporting parking lot operations.
💡 This final view consolidates all components, illustrating the full system design.
💡 The design uses abstraction, inheritance, composition, and encapsulation to model a flexible parking lot.
class Vehicle: # STEP 1
def __init__(self, license_plate):
self.license_plate = license_plate
def get_size(self): # Abstract method
pass
class Car(Vehicle): # STEP 2
def get_size(self):
return 1
class Motorcycle(Vehicle): # STEP 2
def get_size(self):
return 0.5
class ParkingSpot: # STEP 3
def __init__(self, spot_id, size):
self.spot_id = spot_id
self.size = size
self.occupied = False
self.vehicle = None
class ParkingLot: # STEP 4
def __init__(self):
self.spots = []
self.available_spots = 0
def add_parking_spot(self, spot): # STEP 5
self.spots.append(spot)
if not spot.occupied:
self.available_spots += 1
def find_spot_for_vehicle(self, vehicle): # STEP 6
for spot in self.spots:
if not spot.occupied and spot.size >= vehicle.get_size():
return spot
return None
def park_vehicle(self, vehicle): # STEP 7
spot = self.find_spot_for_vehicle(vehicle)
if spot is None:
return False
spot.vehicle = vehicle
spot.occupied = True
self.available_spots -= 1
return True
def remove_vehicle(self, vehicle): # STEP 8
for spot in self.spots:
if spot.vehicle == vehicle:
spot.vehicle = None
spot.occupied = False
self.available_spots += 1
return True
return False
class Truck(Vehicle): # STEP 9
def get_size(self):
return 2
📊
Design a Parking Lot - LLD with OOP (Classes, Patterns, Extensibility) - Watch the Algorithm Execute, Step by Step
Watching this step-by-step helps you understand how each class and relationship contributes to the overall design, making complex OOP concepts tangible.
Step 1/10
·Active fill★Answer cell
Defines an abstract base class for vehicles to enforce a common interface.
«abstract»Vehicle
+license_plate: string
+get_size()
Demonstrates inheritance and polymorphism for vehicle types.
«abstract»Vehicle
+license_plate: string
+get_size()
Car
+get_size()
Motorcycle
+get_size()
Car ▷ VehicleMotorcycle ▷ Vehicle
Encapsulates parking spot properties and occupancy state.
ParkingSpot
+spot_id: string
+size: float
+occupied: bool
+vehicle: Vehicle
Aggregates parking spots and tracks availability.
ParkingLot
+spots: List[ParkingSpot]
+available_spots: int
Shows aggregation and dynamic spot addition.
ParkingLot
+spots: List[ParkingSpot]
+available_spots: int
+add_parking_spot()
ParkingSpot
+spot_id: string
+size: float
+occupied: bool
+vehicle: Vehicle
ParkingLot ◇ ParkingSpot
Implements search logic to find suitable parking spot.
ParkingLot
+spots: List[ParkingSpot]
+available_spots: int
+find_spot_for_vehicle()
«abstract»Vehicle
+license_plate: string
+get_size()
ParkingSpot
+spot_id: string
+size: float
+occupied: bool
+vehicle: Vehicle
ParkingLot ◇ ParkingSpot
Combines search and update to park vehicles.
ParkingLot
+spots: List[ParkingSpot]
+available_spots: int
+park_vehicle()
+find_spot_for_vehicle()
«abstract»Vehicle
+license_plate: string
+get_size()
ParkingSpot
+spot_id: string
+size: float
+occupied: bool
+vehicle: Vehicle
ParkingLot ◇ ParkingSpot
Handles vehicle removal and spot freeing.
ParkingLot
+spots: List[ParkingSpot]
+available_spots: int
+remove_vehicle()
+park_vehicle()
+find_spot_for_vehicle()
«abstract»Vehicle
+license_plate: string
+get_size()
ParkingSpot
+spot_id: string
+size: float
+occupied: bool
+vehicle: Vehicle
ParkingLot ◇ ParkingSpot
Demonstrates extensibility by adding new vehicle types.
«abstract»Vehicle
+license_plate: string
+get_size()
Truck
+get_size()
Truck ▷ Vehicle
Complete design showing abstraction, inheritance, composition, and encapsulation.
«abstract»Vehicle
+license_plate: string
+get_size()
Car
+get_size()
Motorcycle
+get_size()
Truck
+get_size()
ParkingSpot
+spot_id: string
+size: float
+occupied: bool
+vehicle: Vehicle
ParkingLot
+spots: List[ParkingSpot]
+available_spots: int
+add_parking_spot()
+find_spot_for_vehicle()
+park_vehicle()
+1 more
Car ▷ VehicleMotorcycle ▷ VehicleTruck ▷ VehicleParkingLot ◇ ParkingSpot
Key Takeaways
✓ Abstraction and inheritance allow modeling different vehicle types with a common interface.
This is hard to see from code alone because the polymorphic behavior is implicit; visualization makes it explicit.
✓ Composition is used to aggregate ParkingSpot objects inside ParkingLot, showing real-world containment.
Visualizing relationships clarifies how objects collaborate and depend on each other.
✓ The design supports extensibility by allowing new vehicle types without modifying existing classes.
Seeing the addition of Truck class demonstrates the Open/Closed Principle concretely.
Practice
(1/5)
1. In which scenario is encapsulation most beneficial when designing a class?
easy
A. When you want to expose all internal data directly for easy access
B. When you want to bundle data and methods while restricting direct access to internal state
C. When you want to avoid using any access modifiers and rely on global variables
D. When you want to implement multiple inheritance to reuse code
Solution
Step 1: Understand encapsulation purpose
Encapsulation bundles data and methods and restricts direct access to internal state to protect object integrity.
Step 2: Analyze options
When you want to expose all internal data directly for easy access exposes internal data directly, violating encapsulation. When you want to avoid using any access modifiers and rely on global variables ignores access control, risking data corruption. When you want to implement multiple inheritance to reuse code relates to inheritance, not encapsulation.
Final Answer:
Option B -> Option B
Quick Check:
Encapsulation is about bundling and controlled access, not exposing all data or inheritance.
Hint: Encapsulation bundles and hides, not exposes [OK]
Common Mistakes:
Confusing encapsulation with inheritance
Thinking encapsulation means no access at all
Believing global variables are encapsulated
2. When a class inherits from multiple classes that have a method with the same name, describe the step-by-step process the Method Resolution Order (MRO) uses to determine which method is called.
easy
A. MRO uses a linearization algorithm that merges the order of parents and their ancestors to find the method.
B. MRO searches the first parent class fully before moving to the next parent class.
C. MRO always calls the method from the last parent class listed in the inheritance.
D. MRO randomly picks the method from any parent class that defines it.
Solution
Step 1: Understand naive search
MRO does not simply search the first parent class fully before moving to the next; it uses a more sophisticated approach.
Step 2: Recognize MRO linearization
MRO uses a specific linearization (like C3 linearization) that merges parent classes and their ancestors in a consistent order.
Step 3: Eliminate incorrect options
MRO always calls the method from the last parent class listed in the inheritance is incorrect because the last parent is not always chosen; order and ancestors matter. MRO randomly picks the method from any parent class that defines it is incorrect because MRO is deterministic, not random.
Final Answer:
Option A -> Option A
Quick Check:
MRO merges inheritance hierarchies to find the correct method in a predictable order.
Hint: MRO = deterministic linearization of inheritance graph
Common Mistakes:
Assuming simple left-to-right search suffices
Believing last parent always overrides
Thinking method choice is random
3. In which scenario would you rely on method overloading rather than method overriding to achieve polymorphism?
easy
A. When you want to dynamically bind method calls using a virtual table (vtable)
B. When you want to provide multiple behaviors for the same method name based on different parameter types or counts within the same class
C. When you want to change the behavior of a method at runtime depending on the object's actual type
D. When you want a subclass to provide a specific implementation of a method declared in its superclass
Solution
Step 1: Understand method overloading
Method overloading occurs within the same class and involves multiple methods with the same name but different parameter lists, resolved at compile time.
Step 2: Contrast with overriding
Method overriding involves a subclass redefining a method from its superclass, resolved at runtime via dynamic dispatch.
Step 3: Analyze options
When you want to provide multiple behaviors for the same method name based on different parameter types or counts within the same class correctly describes overloading's use case. Options A, B, and C describe overriding or runtime polymorphism concepts.
Final Answer:
Option B -> Option B
Quick Check:
Overloading is about compile-time resolution based on parameters, not runtime behavior changes.
Hint: Overloading = same method name, different parameters, compile-time binding
4. You have a payment processing system that currently uses multiple if-else statements to handle different payment methods like credit card, UPI, and net banking. The system needs to be extended frequently with new payment methods without modifying existing code. Which design approach best addresses this requirement?
easy
A. Use a brute force approach with nested if-else statements for each payment method.
B. Implement a strategy pattern where each payment method is encapsulated in its own class implementing a common interface.
C. Use a recursive function that selects payment methods based on input parameters.
D. Apply a greedy algorithm to select the payment method with the lowest processing fee.
Solution
Step 1: Understand the problem of frequent extension
The system requires adding new payment methods without modifying existing code, which violates the open-closed principle if using if-else chains.
Step 2: Identify the design pattern that encapsulates behaviors
The strategy pattern encapsulates each payment method in its own class implementing a common interface, allowing easy extension by adding new classes without changing existing code.
Final Answer:
Option B -> Option B
Quick Check:
Strategy pattern replaces conditionals with polymorphism [OK]
Hint: Replacing conditionals with polymorphism enables easy extension [OK]
Common Mistakes:
Thinking recursion or greedy algorithms solve extensibility here
5. Considering extensibility in the Snake and Ladder game design, what is a key trade-off when embedding snakes and ladders directly as attributes inside the Board class versus modeling them as separate entities?
medium
A. Modeling snakes and ladders as separate entities increases runtime complexity significantly
B. Embedding snakes and ladders inside Board simplifies design but reduces flexibility to add new types of board elements later
C. Embedding snakes and ladders inside Board improves encapsulation and makes the Board immutable
D. Modeling snakes and ladders separately forces duplication of position data, increasing memory usage unnecessarily
Solution
Step 1: Embedding snakes/ladders inside Board
This approach simplifies initial design but tightly couples Board to these elements.
Step 2: Impact on extensibility
Tightly coupled design makes it harder to add new board elements (e.g., portals, traps) without modifying Board.
Step 3: Modeling as separate entities
Allows easy extension by adding new element types without changing Board internals.
Step 4: Complexity and memory considerations
Separate entities add minimal overhead; runtime complexity impact is negligible.
Final Answer:
Option B -> Option B
Quick Check:
Trade-off is between simplicity and extensibility, not runtime complexity or memory bloat.
Hint: Tight coupling simplifies now but blocks future extensions [OK]
Common Mistakes:
Thinking separate entities cause big runtime overhead
Believing embedding improves encapsulation and immutability