💡 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 a large software system, when would applying the Interface Segregation Principle (ISP) be most beneficial?
easy
A. When clients depend on interfaces that contain methods they do not use, causing unnecessary implementation burden.
B. When all clients require the exact same set of methods, so a single fat interface simplifies design.
C. When you want to enforce a strict inheritance hierarchy with minimal interfaces.
D. When you want to reduce the number of interfaces to simplify the codebase.
Solution
Step 1: Understand ISP's goal
ISP aims to prevent clients from depending on methods they don't use, avoiding fat interfaces that force unnecessary implementations.
Step 2: Analyze options
When clients depend on interfaces that contain methods they do not use, causing unnecessary implementation burden. correctly identifies the scenario where ISP helps by splitting fat interfaces. When all clients require the exact same set of methods, so a single fat interface simplifies design. describes a scenario where ISP is less needed. When you want to enforce a strict inheritance hierarchy with minimal interfaces. confuses inheritance hierarchy with interface segregation. When you want to reduce the number of interfaces to simplify the codebase. incorrectly assumes fewer interfaces always simplify design, ignoring interface misuse.
Final Answer:
Option A -> Option A
Quick Check:
ISP is about splitting interfaces to avoid forcing clients to implement unused methods.
Hint: ISP splits interfaces so clients only depend on what they use.
Common Mistakes:
Believing fewer interfaces always mean better design
Thinking ISP applies when all clients use all methods
Confusing ISP with inheritance hierarchy rules
2. You are designing a system that manages user accounts and sends notification emails. According to the Single Responsibility Principle, how should you organize these responsibilities?
easy
A. Separate user account management and email notification into different classes because each has a different reason to change.
B. Combine user account management and email notification in one class because they are related to users.
C. Put all user-related functionalities, including notifications, into a single class to reduce the number of classes.
D. Create one class for user management and embed email notification logic inside its methods to simplify interactions.
Solution
Step 1: Identify reasons to change
User account management changes when user data or authentication changes; email notifications change when messaging or delivery requirements change.
Step 2: Apply SRP
Since these reasons to change differ, they should be separated into different classes to avoid coupling unrelated changes.
Step 3: Evaluate other options
Options A, C, and D combine responsibilities, increasing coupling and reducing cohesion, violating SRP.
Final Answer:
Option A -> Option A
Quick Check:
Separate classes for distinct reasons to change -> SRP compliant.
Hint: One class, one reason to change.
Common Mistakes:
Assuming related domain means same responsibility.
Combining functionalities to reduce class count.
Embedding multiple responsibilities for convenience.
3. Which of the following is a common trade-off when using a Facade pattern in a large system?
medium
A. Facade can hide too much complexity, making it hard to access advanced features of subsystems
B. Facade increases coupling between client and subsystems by exposing detailed interfaces
C. Facade always adds significant runtime overhead due to extra method calls
D. Facade requires changing the underlying subsystem interfaces to work properly
Solution
Step 1: Recall Facade's purpose
Facade simplifies complex subsystems by providing a unified interface.
Step 2: Analyze trade-offs
While Facade simplifies usage, it can hide advanced features, limiting flexibility.
Step 3: Evaluate other options
A is incorrect because Facade reduces coupling by hiding subsystem details. C is incorrect; Facade's overhead is minimal. D is wrong; Facade does not require changing subsystems.
Final Answer:
Option A -> Option A
Hint: Facade hides complexity but may hide power
Common Mistakes:
Believing Facade increases coupling instead of reducing it
Assuming Facade adds heavy runtime overhead
Thinking Facade requires modifying subsystems
4. Which of the following statements about the Liskov Substitution Principle is INCORRECT?
medium
A. A subclass can strengthen preconditions of an inherited method to ensure better input validation.
B. A subclass must not weaken postconditions of an inherited method.
C. Covariance in return types is allowed under LSP.
D. Contravariance in method parameter types is allowed under LSP.
Solution
Step 1: Recall LSP precondition rule
Subclasses must not strengthen preconditions; they can only maintain or weaken them.
Step 2: Analyze each statement
A subclass can strengthen preconditions of an inherited method to ensure better input validation. is incorrect because strengthening preconditions breaks substitutability. A subclass must not weaken postconditions of an inherited method. is correct; subclasses can weaken postconditions. Covariance in return types is allowed under LSP. is correct; covariance in return types is allowed. Contravariance in method parameter types is allowed under LSP. is correct; contravariance in parameter types is allowed.
Final Answer:
Option A -> Option A
Quick Check:
Strengthening preconditions violates LSP.
Hint: Preconditions can only be weakened, not strengthened, in subclasses.
Common Mistakes:
Confusing precondition and postcondition rules
Believing strengthening preconditions is safe
Misunderstanding covariance and contravariance
5. If the Snake and Ladder game is extended to support multiple concurrent games running in parallel, which design consideration is most critical to avoid shared state bugs?
hard
A. Ensuring each game instance has its own independent GameController and Player objects
B. Centralizing player positions in a global data structure accessible by all games
C. Sharing the Dice instance across games to reduce resource usage
D. Using a singleton pattern for the Board class to ensure consistency across games
Solution
Step 1: Singleton Board pattern
Incorrect: Singleton would cause shared state across games, leading to conflicts.
Step 2: Independent GameController and Player per game
Correct: Isolates state per game, preventing interference.
Step 3: Sharing Dice instance
Incorrect: Shared Dice can cause race conditions or inconsistent rolls.
Step 4: Global player positions
Incorrect: Global state breaks isolation and causes bugs.
Final Answer:
Option A -> Option A
Quick Check:
Isolating state per game instance is key for concurrency safety.
Hint: Isolate state per game instance to avoid concurrency bugs [OK]
Common Mistakes:
Using singleton for shared components without considering concurrency
Sharing mutable objects like Dice across threads
Centralizing state globally ignoring isolation
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.