Bird
Raised Fist0

Car Rental System - Fleet, Booking, Pricing, Damage, Return

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Design a Car Rental System - Fleet, Booking, Pricing, Damage, Return
40 min ยท medium
medium

Design a car rental system that manages a fleet of vehicles, supports booking with pricing rules, handles damage reports, and processes vehicle returns with state updates.

  • Fleet consists of multiple vehicle types (Sedan, SUV, Hatchback) with unique pricing per type.
  • Booking must prevent double-booking of the same vehicle concurrently.
  • Pricing includes base price plus dynamic adjustments (e.g., weekend surcharge).
  • Damage reports affect vehicle availability and may trigger maintenance.
  • Return process updates vehicle status and calculates final charges.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define Vehicle Hierarchy and Fleet Management
Design the Vehicle class hierarchy and how the fleet is managed. Consider vehicle types and shared state.
๐Ÿ’ก Hint
Use an abstract class for Vehicle to hold common state like licensePlate and vehicleType. Use enum for vehicle types to avoid runtime errors.
abstract class Vehicle(licensePlate, vehicleType) - abstract because it holds shared instance STATE; interface cannot hold fields in Java; VehicleType as enum ensures compile-time safety.
Mid: Uses abstract class Vehicle with subclasses for types. Senior: Explains why interface can't hold licensePlate state and why enum VehicleType prevents typo bugs.
โš ๏ธCommon Wrong Answer
Using interface Vehicle with default methods but no fields leads to duplicated licensePlate in subclasses and inconsistent state.
Step 2
Implement Booking with Concurrency Control
Design booking logic to prevent double-booking of the same vehicle by concurrent threads.
๐Ÿ’ก Hint
Use synchronized boolean bookVehicle(Vehicle v) method to atomically check and set vehicle availability status.
synchronized boolean bookVehicle(Vehicle v) - atomic check-and-set of vehicle availability to prevent race conditions and double-booking.
Mid: Uses synchronized method for booking. Senior: Traces exact thread interleaving where Thread 1 reads AVAILABLE, context switch, Thread 2 reads AVAILABLE, both book leading to double-booking without synchronization.
โš ๏ธCommon Wrong Answer
Using unsynchronized booking leads to race conditions where multiple threads can book the same vehicle simultaneously.
Step 3
Design Pricing Strategy with Extensibility
Design pricing calculation supporting base price and dynamic adjustments like weekend surcharges.
๐Ÿ’ก Hint
Use Strategy pattern with PricingStrategy interface and concrete implementations; interface is suitable here as no shared state is needed.
interface PricingStrategy { double calculatePrice(RentalPeriod p); } - interface used because pricing strategies have no shared instance state; supports Open/Closed principle.
Mid: Uses Strategy pattern with PricingStrategy interface. Senior: Explains why interface is chosen over abstract class due to statelessness and how it supports adding new pricing rules without modifying existing code.
โš ๏ธCommon Wrong Answer
Using abstract class with state for pricing strategies leads to unnecessary coupling and violates Open/Closed principle.
Step 4
Handle Damage Reporting and Vehicle Status Updates
Design how damage reports affect vehicle availability and trigger maintenance workflows.
๐Ÿ’ก Hint
Use enum VehicleStatus with values AVAILABLE, BOOKED, UNDER_MAINTENANCE; update status atomically with synchronized methods.
volatile VehicleStatus status + synchronized void reportDamage(Vehicle v, DamageReport d) - volatile ensures visibility of status changes across threads; synchronized ensures atomic status update and maintenance trigger.
Mid: Uses enum VehicleStatus and synchronized damage reporting. Senior: Explains volatile visibility guarantees and how synchronized prevents lost updates in concurrent damage reports.
โš ๏ธCommon Wrong Answer
Using String for status leads to typo bugs; no synchronization causes lost updates and inconsistent vehicle states.
Step 5
Design Vehicle Return Process with Final Charge Calculation
Design the return process that updates vehicle status and calculates final charges including damage fees.
๐Ÿ’ก Hint
Use synchronized method returnVehicle(Vehicle v, ReturnInfo info) to atomically update status and calculate charges; use composition to include DamageReport in ReturnInfo.
synchronized double returnVehicle(Vehicle v, ReturnInfo info) - atomic update of vehicle status and charge calculation; composition relationship between ReturnInfo and DamageReport ensures lifecycle ownership.
Mid: Uses synchronized returnVehicle method. Senior: Explains atomicity prevents race conditions where concurrent returns corrupt vehicle state or charge calculation.
โš ๏ธCommon Wrong Answer
Non-synchronized return leads to race conditions updating vehicle status and incorrect final charges.
๐Ÿ”Œ
API Design Challenge
Design APIs for booking vehicles, reporting damage, and returning vehicles that ensure thread safety and data consistency.
  • Use synchronized methods or locks to prevent race conditions in booking and return processes.
  • Design clear input and output data structures for damage reports and return info to encapsulate related data.
  • Consider using enums for vehicle status and types to improve type safety and reduce runtime errors.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract Vehicle class with instance state and VehicleType enum
  • synchronized booking method preventing double-booking race conditions
  • PricingStrategy interface for extensible pricing rules
  • volatile VehicleStatus field with synchronized damage reporting
  • synchronized returnVehicle method with composition for damage reports
Good to Have - 30%
  • Use of enums for all types and statuses
  • Clear lifecycle ownership in composition relationships
  • Explanation of concurrency interleavings causing bugs
  • Open/Closed principle adherence in pricing design
Bonus - 10%
  • Detailed concurrency scenario tracing
  • Use of double-checked locking if singleton fleet manager implemented
Using String for VehicleType or VehicleStatus leads to typo bugs and runtime crashes because strings are prone to misspellings and lack compile-time checks, causing unpredictable failures.
Unsynchronized booking method causes race conditions and double-booking, where multiple threads can book the same vehicle simultaneously, leading to inconsistent system state and customer dissatisfaction.
Abstract class with state used for stateless pricing strategies violates Open/Closed principle by coupling pricing logic and state, making it harder to extend pricing rules without modifying existing code.
No volatile on shared status fields causes visibility issues across threads, where one thread's update to vehicle status may not be seen by others promptly, leading to stale or inconsistent reads.
Non-atomic damage reporting leads to lost updates and inconsistent vehicle states, where concurrent damage reports overwrite each other or leave the vehicle in an incorrect status.
โœ…
Model Answer
โ–ผ Reveal

The design uses an abstract Vehicle class with licensePlate and VehicleType enum to hold shared state, ensuring type safety and preventing runtime errors. Booking is synchronized to atomically check and set vehicle availability, preventing double-booking race conditions. Pricing uses a stateless PricingStrategy interface supporting extensibility via the Open/Closed principle. VehicleStatus is a volatile enum field ensuring visibility of status changes across threads, with synchronized damage reporting to prevent lost updates. The return process uses a synchronized method that atomically updates vehicle status and calculates final charges, with composition between ReturnInfo and DamageReport to maintain lifecycle ownership of damage data.

  • Use abstract class Vehicle to hold shared instance state licensePlate and VehicleType enum for type safety.
  • Synchronize booking method to atomically check and set vehicle availability, preventing race conditions.
  • Use PricingStrategy interface for stateless pricing rules, supporting Open/Closed principle and easy extensibility.
  • Declare VehicleStatus as volatile to ensure visibility of status changes across threads.
  • Synchronize damage reporting to atomically update vehicle status and trigger maintenance workflows.
  • Use composition between ReturnInfo and DamageReport to tie damage data lifecycle to return process.
  • Synchronize returnVehicle method to atomically update vehicle status and calculate final charges, preventing race conditions.
  • abstract class Vehicle holds licensePlate and VehicleType enum - interface cannot hold instance state in Java.
  • VehicleType enum prevents runtime typo bugs and supports exhaustive switch statements.
  • synchronized booking method ensures atomic check-and-set of vehicle availability to prevent double-booking race conditions.
  • PricingStrategy interface supports stateless pricing strategies and Open/Closed principle for extensibility.
  • volatile VehicleStatus field ensures visibility of status changes across threads.
  • synchronized damage reporting method prevents lost updates and inconsistent vehicle states.
  • Composition relationship between ReturnInfo and DamageReport ensures damage data lifecycle is tied to return process.
  • synchronized returnVehicle method atomically updates vehicle status and calculates final charges to prevent race conditions.
  • Use of enums for statuses and types improves type safety and IDE support.
  • Avoid String for types/status to prevent runtime errors and poor maintainability.

Practice

(1/5)
1. In the Food Ordering System, the OrderManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is this the correct choice?
easy
A. Factory pattern, because it controls object creation centrally.
B. Builder pattern, to construct complex Order objects step-by-step.
C. Singleton pattern, to ensure only one instance manages all orders globally.
D. Prototype pattern, to clone existing OrderManager instances efficiently.

Solution

  1. Step 1: Identify the pattern from constructor and method signature

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single instance is created and accessed globally.
  2. Step 2: Why Singleton fits this use case

    OrderManager must coordinate all orders centrally to avoid conflicting states; Singleton guarantees a single shared instance.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
2. In designing the Workflow Engine, should the Condition component be an abstract class or an interface, and why?
easy
A. Interface, because Conditions only define a contract for evaluation without shared state, allowing multiple implementations.
B. Abstract class, because Java interfaces cannot have default methods for condition evaluation logic.
C. Abstract class, because Conditions share common state and behavior that subclasses must inherit.
D. Interface, because it allows Conditions to hold mutable state safely across threads.

Solution

  1. Step 1: Analyze Condition's role

    Conditions define evaluation logic without requiring shared state; they represent a contract for boolean checks.
  2. Step 2: Interface vs Abstract class

    Interface suits stateless behavior contracts and allows multiple inheritance; abstract class implies shared state, which is not needed and limits flexibility.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Condition defines behavior only -> interface [check]
Hint: Stateless behavior contracts -> interface, not abstract class [check]
Common Mistakes:
3. In the Elevator System design, the ElevatorController class uses a private constructor and a static synchronized getInstance() method to provide a single global access point. Which design pattern is applied here and why is it the correct choice?
easy
A. Factory pattern, because it creates ElevatorController instances on demand.
B. Singleton pattern, to ensure only one ElevatorController manages all elevators centrally.
C. Builder pattern, to incrementally construct ElevatorController with complex configuration.
D. Prototype pattern, to clone ElevatorController for each elevator instance.

Solution

  1. Step 1: Identify Singleton usage

    The private constructor and static synchronized getInstance() method restrict instantiation to one object, ensuring a single ElevatorController.
  2. Step 2: Why Singleton fits ElevatorController

    ElevatorController coordinates multiple elevators and floor queues centrally, so multiple instances would cause inconsistent state and scheduling conflicts.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Singleton ensures one global ElevatorController instance [check]
Hint: Singleton restricts to one instance for centralized control [check]
Common Mistakes:
4. In a multiplayer card game, the Hand class has a method addCard(Card card) that is not synchronized. Two threads concurrently add cards to the same hand. Which exact sequence causes a race condition leading to lost cards?
medium
A. Thread 1 reads current card list size -> context switch -> Thread 2 reads same size -> both add at same index, overwriting one card.
B. Thread 1 locks hand, adds card, unlocks; Thread 2 waits and then adds card safely.
C. Thread 1 and Thread 2 both add cards simultaneously without any interleaving.
D. Thread 1 and Thread 2 both call addCard but cards are queued and processed sequentially.

Solution

  1. Step 1: Identify the check-then-act gap

    Without synchronization, both threads read the current size of the card list before either adds their card.
  2. Step 2: Trace interleaving causing overwrite

    Thread 1 reads size N, context switches; Thread 2 reads size N; both add at index N, causing one card to overwrite the other.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized addCard -> check-then-act race -> lost card [check]
Hint: Check size -> context switch -> duplicate index add -> lost card [check]
Common Mistakes:
5. In a multi-threaded inventory update method updateStock() without synchronization, which exact sequence causes a race condition leading to incorrect stock count?
medium
A. Thread 1 and Thread 2 both wait on a lock, preventing race conditions
B. Thread 1 reads stock=10, Thread 2 reads stock=10, both add 5, final stock=15
C. Thread 1 writes stock=15, Thread 2 reads stock=15, then writes stock=20 correctly
D. Thread 1 reads stock=10, context switch before update, Thread 2 reads stock=10, both write stock=15, losing one update

Solution

  1. Step 1: Trace the check-then-act gap

    Thread 1 reads stock=10 but is preempted before writing update. Thread 2 reads the same stock=10 and writes stock=15. Thread 1 resumes and overwrites with stock=15, losing Thread 2's update.
  2. Step 2: Why other options fail

    Thread 1 reads stock=10, Thread 2 reads stock=10, both add 5, final stock=15 incorrectly assumes final stock=15 after both add 5 (should be 20). Thread 1 writes stock=15, Thread 2 reads stock=15, then writes stock=20 correctly shows correct sequence with no race. Thread 1 and Thread 2 both wait on a lock, preventing race conditions describes locking, which is absent here.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Unsynchronized read-update-write -> lost update race condition [check]
Hint: Unsynchronized read-update-write causes lost updates [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard