Bird
Raised Fist0

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

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
▶
Class Trace
Step 1/4
Car abstract class

Car is an abstract class holding shared state like licensePlate and volatile isAvailable flag.

🎯 Tests whether candidate knows abstract class is needed because Car holds instance state which interfaces cannot hold in Java.
public abstract class Car {
    private String licensePlate;
    private volatile boolean isAvailable;
    public synchronized boolean isAvailable() {
        return isAvailable;
    }
    public synchronized void setAvailable(boolean available) {
        this.isAvailable = available;
    }
}
FleetManager composition

FleetManager composes a list of Car objects, managing their lifecycle tightly (composition).

🎯 Tests whether candidate understands composition lifecycle: Cars cannot exist independently without FleetManager.
public class FleetManager {
    private final List<Car> cars = new ArrayList<>();
    public synchronized void addCar(Car c) {
        cars.add(c);
    }
    public synchronized boolean parkCar(Car c) {
        if (!c.isAvailable()) return false;
        c.setAvailable(false);
        return true;
    }
}
PricingStrategy interface and ReturnProcessor aggregation

PricingStrategy is an interface to allow multiple pricing algorithms; ReturnProcessor aggregates PricingStrategy to calculate fees.

🎯 Tests whether candidate knows interface enables OCP for pricing and aggregation means PricingStrategy can exist independently of ReturnProcessor.
public interface PricingStrategy {
    double calcPrice(Booking b);
}
public class ReturnProcessor {
    private PricingStrategy pricingStrategy;
    public ReturnProcessor(PricingStrategy strategy) {
        this.pricingStrategy = strategy;
    }
    public void processReturn(Booking b) {
        double price = pricingStrategy.calcPrice(b);
        // process payment
    }
}
volatile on isAvailable in Car

The isAvailable flag in Car is volatile to ensure visibility across threads without locking for reads.

🎯 Tests whether candidate understands volatile ensures visibility of isAvailable changes across threads, preventing stale reads.
private volatile boolean isAvailable;
public synchronized void setAvailable(boolean available) {
    this.isAvailable = available;
}
public synchronized boolean isAvailable() {
    return isAvailable;
}
📐
Class Diagram
Class Diagram
       +----------------+
       |    <<Abstract>>|
       |       Car      |
       +----------------+
       | - licensePlate : String
       | - isAvailable : volatile boolean
       +----------------+
       | + public synchronized boolean isAvailable()
       | + public synchronized void setAvailable(boolean)
       +----------------+
              │ extends
       +----------------+
       |  FleetManager  |
       +----------------+
       | - cars : List<Car> (composition)
       +----------------+
       | + public synchronized void addCar(Car c)
       | + public synchronized boolean parkCar(Car c)
       +----------------+
              │
              │ association
       +----------------------+
       |  PricingStrategy     |
       |    <<Interface>>     |
       +----------------------+
       | + public double calcPrice(Booking b)
       +----------------------+
              │  realization
       +----------------------+
       |  ReturnProcessor     |
       +----------------------+
       | - pricingStrategy : PricingStrategy (aggregation)
       +----------------------+
       | + public void processReturn(Booking b)
       +----------------------+

This class diagram illustrates key Java design decisions in the Car Rental System. Car is an abstract class because it holds mutable instance state like licensePlate and a volatile isAvailable flag, which interfaces cannot hold in Java. FleetManager composes Car objects, enforcing strong lifecycle ownership, so Cars cannot exist independently, demonstrating composition. PricingStrategy is an interface to enable the Open-Closed Principle (OCP) allowing multiple pricing algorithms without modifying ReturnProcessor, which aggregates PricingStrategy, showing independent lifecycle. The volatile keyword on isAvailable in Car ensures visibility of availability status changes across threads, preventing stale reads without full locking. These decisions together enforce SOLID principles and concurrency correctness in Java.

Primary Focus

✓ Abstract class state vs interface + Composition lifecycle + Strategy pattern OCP + Singleton volatile

Practice

(1/5)
1. In designing the Card abstraction, should it be an abstract class or an interface? Consider that cards have immutable state (suit, rank) and common behavior like compareTo(). Which is the best choice and why?
easy
A. Interface, to allow multiple inheritance of card behaviors in subclasses.
B. Interface, because cards only define behavior and no state should be stored.
C. Abstract class, because cards share immutable state and common method implementations.
D. Abstract class, because interfaces cannot have any method implementations.

Solution

  1. Step 1: Analyze state and behavior requirements

    Cards have immutable state (suit, rank) and common behavior like compareTo() that can be implemented once.
  2. Step 2: Abstract class vs interface capabilities

    Abstract classes can hold state and provide method implementations, while interfaces (pre-Java 8) cannot hold state and only declare methods.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Immutable state + shared behavior -> abstract class [check]
Hint: Stateful common code -> abstract class, stateless behavior -> interface [check]
Common Mistakes:
2. In a Java class hierarchy, a base class defines a final method that calls several abstract methods which subclasses must implement. Which design pattern is this an example of?
easy
A. Template Method Pattern, because the base class defines the algorithm skeleton and subclasses fill in steps.
B. Strategy Pattern, because subclasses provide interchangeable algorithms.
C. Factory Method Pattern, because the base class creates objects via subclass overrides.
D. Decorator Pattern, because behavior is added dynamically by subclasses.

Solution

  1. Step 1: Identify the role of the final method

    The base class's final method defines the fixed algorithm skeleton, preventing subclasses from changing the overall flow.
  2. Step 2: Recognize abstract methods as extension points

    Subclasses implement abstract methods to fill in specific steps, matching the Template Method pattern's intent.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Base class controls algorithm flow; subclasses customize steps [check]
Hint: Final method fixes flow; subclasses fill steps [check]
Common Mistakes:
3. A class has a private constructor and a static synchronized getInstance() method returning the single instance. Which pattern is applied and why is this the CORRECT use case?
easy
A. Singleton pattern, because it ensures only one instance exists and controls concurrent access.
B. Factory Method pattern, because it controls object creation with a static method.
C. Builder pattern, because it constructs complex objects step-by-step.
D. Prototype pattern, because it clones existing instances to avoid new construction.

Solution

  1. Step 1: Identify Singleton pattern characteristics

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single instance with thread-safe lazy initialization.
  2. Step 2: Eliminate other creational patterns

    Factory Method controls subclass instantiation, Builder separates construction from representation, and Prototype clones objects; none enforce single instance or thread safety.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton pattern [check]
Hint: Private constructor + synchronized getInstance() -> Singleton [check]
Common Mistakes:
4. In the Workflow Engine, if the Executor class's executeStep() method is not synchronized, which exact thread interleaving causes two threads to execute the same step concurrently, leading to inconsistent state?
medium
A. Thread 1 finishes executeStep() before Thread 2 starts, so no concurrency issue occurs.
B. Thread 1 and Thread 2 both call executeStep() simultaneously and both complete without interference.
C. Thread 1 checks if step is not running -> true, context switch; Thread 2 checks same -> true; both proceed to execute concurrently.
D. Thread 1 locks the step, but Thread 2 waits indefinitely causing deadlock.

Solution

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

    Without synchronization, Thread 1 checks step status as free, then context switches before marking running.
  2. Step 2: Thread 2 repeats check before Thread 1 marks running

    Thread 2 also sees step free and proceeds, causing both to execute concurrently.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Unsynchronized check-then-act -> double execution race [check]
Hint: Check-then-act without sync -> race condition [check]
Common Mistakes:
5. ElectricCar requires ChargingSpots in the Amazon Locker Service design. What is the minimum change to support this without modifying existing classes?
hard
A. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map externally.
B. Modify Vehicle and Spot classes to add electric-specific fields and methods for charging compatibility.
C. Create a new ElectricCarChargingManager class that extends LockerManager and handles charging spots separately.
D. Add a boolean isElectric field to Vehicle and Spot classes and update existing logic to handle charging.

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Enums VehicleType and SpotType can be extended with new constants without changing existing class logic. Creating ElectricCar subclass extends Vehicle cleanly.
  2. Step 2: Update SpotCompatibility map externally

    Maintaining compatibility mapping outside existing classes avoids modifying them, preserving OCP and avoiding class explosion.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Extend enums + subclass + external compatibility map -> minimal change [check]
Hint: Extend enums + subclass + external map -> no existing class change [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