Bird
Raised Fist0
Interview Prepoop-design-patternshardAmazonGoogleMicrosoftFlipkartSwiggyRazorpayPhonePeCREDZepto

Design a Parking Lot - LLD with OOP (Classes, Patterns, Extensibility)

Choose your preparation mode3 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Steps
setup

Define the Vehicle class

We start by defining the abstract Vehicle class, which will be the base for all vehicle types in the parking lot.

💡 This step establishes the abstraction for vehicles, allowing extensibility for different vehicle types later.
Line:class Vehicle: def __init__(self, license_plate): self.license_plate = license_plate def get_size(self): pass # Abstract method
💡 Vehicle is an abstract class representing any vehicle, enforcing a common interface.
📊
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 fillAnswer 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

  1. Step 1: Understand encapsulation purpose

    Encapsulation bundles data and methods and restricts direct access to internal state to protect object integrity.
  2. 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.
  3. Final Answer:

    Option B -> Option B
  4. 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

  1. 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.
  2. Step 2: Recognize MRO linearization

    MRO uses a specific linearization (like C3 linearization) that merges parent classes and their ancestors in a consistent order.
  3. 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.
  4. Final Answer:

    Option A -> Option A
  5. 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

  1. 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.
  2. Step 2: Contrast with overriding

    Method overriding involves a subclass redefining a method from its superclass, resolved at runtime via dynamic dispatch.
  3. 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.
  4. Final Answer:

    Option B -> Option B
  5. 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
Common Mistakes:
  • Confusing overloading with overriding
  • Thinking overloading involves runtime polymorphism
  • Believing vtable is used for overloading
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

  1. 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.
  2. 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.
  3. Final Answer:

    Option B -> Option B
  4. 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

  1. Step 1: Embedding snakes/ladders inside Board

    This approach simplifies initial design but tightly couples Board to these elements.
  2. Step 2: Impact on extensibility

    Tightly coupled design makes it harder to add new board elements (e.g., portals, traps) without modifying Board.
  3. Step 3: Modeling as separate entities

    Allows easy extension by adding new element types without changing Board internals.
  4. Step 4: Complexity and memory considerations

    Separate entities add minimal overhead; runtime complexity impact is negligible.
  5. Final Answer:

    Option B -> Option B
  6. 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
  • Assuming separate entities cause memory bloat