Machine Coding Problem

Car Rental System

maco60macoAllmarketplacestrategy-patternstate-patternconcurrent-collectionsatomic-reference-locksobserver-notifications
Commonly Asked By:UberLyftHertzTuro

Functional Scope (In-Scope)

  • Dynamic Billing strategies (Strategy Pattern): Delegate base, late, and mileage cost logic dynamically per vehicle category.
  • Safe Atomic Reservations (State Pattern): Enforce bullet-proof status transitions using synchronized lock sweeps and Atomic Reference guards.
  • Observer Notifications telemetry: Register alert triggers to listen to inventory shortages and status adjustments.
  • Damage Assessment returning flows: Audit return variables to calculate damage fees and send vehicles to maintenance states automatically.

Explicit Boundaries (Out-of-Scope)

  • No keyless vehicle ignitions IoT signals: Excludes direct Bluetooth socket streams or physical door locker toggling.
  • No dynamic gas fuel levels detection: Does not hook into real fuel tank sensors.

Clean reference designs demonstrating stateful fleet rentals in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.*;

enum VehicleType { SEDAN, SUV, TRUCK }
enum VehicleStatus { AVAILABLE, RESERVED, RENTED, UNDER_MAINTENANCE }

interface FleetObserver {
    void onVehicleStatusChanged(String vehicleId, VehicleStatus oldStatus, VehicleStatus newStatus);
    void onLowAvailabilityAlert(VehicleType type, int count);
}

interface RentalPricingStrategy {
    double calculateBase(int days);
    double calculateLateFees(int lateHours);
    double calculateMileageFees(double miles);
}

class SedanPricingStrategy implements RentalPricingStrategy {
    @Override public double calculateBase(int days) { return days * 40.0; }
    @Override public double calculateLateFees(int lateHours) { return lateHours * 15.0; }
    @Override public double calculateMileageFees(double miles) { return miles * 0.15; }
}

class SUVPricingStrategy implements RentalPricingStrategy {
    @Override public double calculateBase(int days) { return days * 75.0; }
    @Override public double calculateLateFees(int lateHours) { return lateHours * 25.0; }
    @Override public double calculateMileageFees(double miles) { return miles * 0.25; }
}

class Vehicle {
    private final String id;
    private final VehicleType type;
    private final String licensePlate;
    private final RentalPricingStrategy pricingStrategy;
    private final AtomicReference<VehicleStatus> status = new AtomicReference<>(VehicleStatus.AVAILABLE);

    public Vehicle(String id, VehicleType type, String licensePlate, RentalPricingStrategy strategy) {
        this.id = id;
        this.type = type;
        this.licensePlate = licensePlate;
        this.pricingStrategy = strategy;
    }

    public String getId() { return id; }
    public VehicleType getType() { return type; }
    public String getLicensePlate() { return licensePlate; }
    public RentalPricingStrategy getPricingStrategy() { return pricingStrategy; }
    public VehicleStatus getStatus() { return status.get(); }

    public boolean reserve() {
        return status.compareAndSet(VehicleStatus.AVAILABLE, VehicleStatus.RESERVED);
    }

    public boolean rent() {
        return status.compareAndSet(VehicleStatus.RESERVED, VehicleStatus.RENTED);
    }

    public void release(boolean needsMaintenance) {
        status.set(needsMaintenance ? VehicleStatus.UNDER_MAINTENANCE : VehicleStatus.AVAILABLE);
    }
}

class Reservation {
    private final String id;
    private final String userId;
    private final Vehicle vehicle;
    private final int plannedDays;
    private final long timestamp;

    public Reservation(String id, String userId, Vehicle vehicle, int plannedDays) {
        this.id = id;
        this.userId = userId;
        this.vehicle = vehicle;
        this.plannedDays = plannedDays;
        this.timestamp = System.currentTimeMillis();
    }

    public String getId() { return id; }
    public String getUserId() { return userId; }
    public Vehicle getVehicle() { return vehicle; }
    public int getPlannedDays() { return plannedDays; }
}

class Invoice {
    private final String reservationId;
    private final double baseCost;
    private final double lateFee;
    private final double mileageFee;
    private final double damageFee;
    private final double totalCost;

    public Invoice(String reservationId, double baseCost, double lateFee, double mileageFee, double damageFee) {
        this.reservationId = reservationId;
        this.baseCost = baseCost;
        this.lateFee = lateFee;
        this.mileageFee = mileageFee;
        this.damageFee = damageFee;
        this.totalCost = baseCost + lateFee + mileageFee + damageFee;
    }

    public double getTotalCost() { return totalCost; }

    @Override
    public String toString() {
        return String.format("Invoice[Reservation: %s, Base: $%.2f, Late: $%.2f, Mileage: $%.2f, Damage: $%.2f, Total: $%.2f]",
                reservationId, baseCost, lateFee, mileageFee, damageFee, totalCost);
    }
}

class CarRentalService {
    private final Map<String, Vehicle> fleet = new ConcurrentHashMap<>();
    private final Map<String, Reservation> reservations = new ConcurrentHashMap<>();
    private final List<FleetObserver> observers = new CopyOnWriteArrayList<>();
    private final ReentrantLock systemLock = new ReentrantLock();

    public void registerObserver(FleetObserver obs) { observers.add(obs); }
    public void addVehicle(Vehicle v) { fleet.put(v.getId(), v); }

    public Reservation reserveVehicle(String userId, VehicleType type, int plannedDays) {
        systemLock.lock();
        try {
            Vehicle target = fleet.values().stream()
                    .filter(v -> v.getType() == type && v.getStatus() == VehicleStatus.AVAILABLE)
                    .findFirst()
                    .orElse(null);

            if (target != null && target.reserve()) {
                notifyStatusChange(target.getId(), VehicleStatus.AVAILABLE, VehicleStatus.RESERVED);
                checkLowAvailability(type);
                Reservation res = new Reservation(UUID.randomUUID().toString(), userId, target, plannedDays);
                reservations.put(res.getId(), res);
                return res;
            }
            return null;
        } finally {
            systemLock.unlock();
        }
    }

    public boolean pickupVehicle(String reservationId) {
        Reservation res = reservations.get(reservationId);
        if (res != null && res.getVehicle().rent()) {
            notifyStatusChange(res.getVehicle().getId(), VehicleStatus.RESERVED, VehicleStatus.RENTED);
            return true;
        }
        return false;
    }

    public Invoice returnVehicle(String reservationId, int actualDays, int lateHours, double extraMiles, double damageFee) {
        Reservation res = reservations.get(reservationId);
        if (res == null) return null;

        Vehicle v = res.getVehicle();
        RentalPricingStrategy pricing = v.getPricingStrategy();

        double base = pricing.calculateBase(res.getPlannedDays());
        double late = pricing.calculateLateFees(lateHours);
        double mileage = pricing.calculateMileageFees(extraMiles);

        boolean needsMaint = damageFee > 1000.0;
        v.release(needsMaint);
        notifyStatusChange(v.getId(), VehicleStatus.RENTED, v.getStatus());

        Invoice inv = new Invoice(reservationId, base, late, mileage, damageFee);
        reservations.remove(reservationId);
        return inv;
    }

    private void notifyStatusChange(String id, VehicleStatus oldS, VehicleStatus newS) {
        for (FleetObserver obs : observers) obs.onVehicleStatusChanged(id, oldS, newS);
    }

    private void checkLowAvailability(VehicleType type) {
        long count = fleet.values().stream().filter(v -> v.getType() == type && v.getStatus() == VehicleStatus.AVAILABLE).count();
        if (count < 2) {
            for (FleetObserver obs : observers) obs.onLowAvailabilityAlert(type, (int) count);
        }
    }
}