Machine Coding Problem

Parking Lot

maco30maco60macoAllutilityinheritanceenumsstrategy-pattern
Commonly Asked By:AmazonGoogleMicrosoftUberWalmart

Requirements & System Scope

Functional Scope (In-Scope)

  • Multi-Floor Parking: The system should support multiple floors with diverse spot types (Motorcycle, Compact, Large).
  • Spot Allocation: Match incoming vehicles automatically to their correct spot type (e.g., Cars in Compact/Large, Trucks only in Large).
  • Ticket Management: Generate a unique, timestamped ticket at entry gates and collect it at exit gates.
  • Fee Calculation: Pluggable hourly or flat-rate dynamic pricing based on total parked duration.
  • Payment Processing: Support cards, cash, or mobile payments with mock verification logic.

Explicit Boundaries (Out-of-Scope)

  • No Real Database Integration: All storage is managed in-memory (singleton orchestrators, local maps) to focus purely on object models.
  • No UI Layer: Interacted with via a simple programmatic test harness or terminal controller.
  • Simplified Hardware Integrations: Barrier arm gates, ticket printers, and display boards are represented by stateless interface mocks.

Class Diagram & Entity Relationships

The structured domain model below shows the hierarchy of a robust Object-Oriented Parking Lot implementation:

Loading...
  • Composition Pattern: ParkingLot acts as a composite anchor containing ParkingFloor arrays which themselves compose ParkingSpot objects.
  • Polymorphic Spot Matching: Type compatibility is managed using enums (VehicleType and SpotType), mapped at runtime.

Design Patterns & SOLID Principles

  • Strategy Pattern (Fee Calculation): Injecting a PricingStrategy interface (with concrete implementations like HourlyPricing and FlatRatePricing) into the billing engine allows easy adjustments without rewriting exit-gate ticket verification.
  • Singleton Pattern (ParkingLot): Orchestrating state across distinct physical gates requires a single, concurrent source of truth (ParkingLotManager) to prevent overallocation or inconsistent occupancy counts.
  • Factory Pattern (Payment Processor): Resolves dynamic payment modes (Cash, Card, UPI) through a factory resolver at checkout, keeping the payment controller decoupling robust.

Core Execution Workflows

1. Vehicle Entry Workflow

  1. Vehicle pulls up to Entry Gate and triggers sensor.
  2. System queries ParkingLotManager for an available spot matching the vehicle type.
  3. If spot is found:
    1. Spot is marked as occupied (atomically).
    2. A unique, concurrent-safe Ticket is created with the entry timestamp.
    3. Ticket is printed and the gate barrier is raised.
  4. If no spots are free, the digital display shows โ€œFULLโ€ and barrier remains closed.

2. Vehicle Exit & Billing Workflow

  1. Vehicle arrives at Exit Gate and scans Ticket.
  2. System calculates duration: duration = currentTime - entryTime.
  3. The active PricingStrategy calculates total fees.
  4. User selects payment method; the PaymentFactory resolves details and issues a checkout transaction.
  5. Upon success, the associated ParkingSpot is released, gate barrier raises, and a receipt prints.

Concurrency & Thread Safety Strategy

In highly concurrent scenarios (e.g., dozens of vehicles entering via multiple gates simultaneously competing for the last available spot), simple collections will suffer from race conditions resulting in double-booking.

  • Thread-Safe Spot Allocation: Use synchronized blocks on spot reservation methods in Java, or local Mutex locks in Python, wrapping both the availability check and the assignment inside a single atomic operation.
  • Atomic ID Generators: Ticket IDs are generated via AtomicInteger / lock-protected class counters to guarantee uniqueness across threads.
  • Concurrent Collections: Store active spot allocations in ConcurrentHashMap to prevent runtime crashes during read-heavy display status queries.

Complete Clean Code Blueprint

Below is a clean, multi-language codebase blueprint showcasing our design patterns and thread-safety strategies in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

enum VehicleType { MOTORCYCLE, CAR, TRUCK }
enum SpotType { MOTORCYCLE, COMPACT, LARGE }

abstract class Vehicle {
    private final String licensePlate;
    private final VehicleType type;

    protected Vehicle(String licensePlate, VehicleType type) {
        this.licensePlate = licensePlate;
        this.type = type;
    }
    public String getLicensePlate() { return licensePlate; }
    public VehicleType getType() { return type; }
}

class Motorcycle extends Vehicle {
    public Motorcycle(String licensePlate) { super(licensePlate, VehicleType.MOTORCYCLE); }
}

class Car extends Vehicle {
    public Car(String licensePlate) { super(licensePlate, VehicleType.CAR); }
}

class Truck extends Vehicle {
    public Truck(String licensePlate) { super(licensePlate, VehicleType.TRUCK); }
}

class ParkingSpot {
    private final String id;
    private final SpotType type;
    private Vehicle currentVehicle;
    private boolean isFree = true;

    public ParkingSpot(String id, SpotType type) {
        this.id = id;
        this.type = type;
    }

    public synchronized boolean isAvailable() { return isFree; }

    public synchronized boolean reserve(Vehicle vehicle) {
        if (!isFree) return false;
        this.currentVehicle = vehicle;
        this.isFree = false;
        return true;
    }

    public synchronized void release() {
        this.currentVehicle = null;
        this.isFree = true;
    }

    public String getId() { return id; }
    public SpotType getType() { return type; }
    public Vehicle getCurrentVehicle() { return currentVehicle; }
}

class ParkingFloor {
    private final String name;
    private final List<ParkingSpot> spots = new CopyOnWriteArrayList<>();

    public ParkingFloor(String name) {
        this.name = name;
    }

    public void addSpot(ParkingSpot spot) {
        spots.add(spot);
    }

    public List<ParkingSpot> getSpots() { return spots; }
    public String getName() { return name; }
}

interface PricingStrategy {
    double calculateFee(long durationMs);
}

class HourlyPricing implements PricingStrategy {
    private final double hourlyRate;
    public HourlyPricing(double rate) { this.hourlyRate = rate; }

    @Override
    public double calculateFee(long durationMs) {
        double hours = Math.ceil(durationMs / 3600000.0);
        return Math.max(1.0, hours) * hourlyRate;
    }
}

class Ticket {
    private static final AtomicInteger counter = new AtomicInteger(1);
    private final String ticketId;
    private final Vehicle vehicle;
    private final ParkingSpot spot;
    private final long entryTime;
    private long exitTime;
    private double fee;
    private boolean isPaid;

    public Ticket(Vehicle vehicle, ParkingSpot spot) {
        this.ticketId = "TKT-" + counter.getAndIncrement();
        this.vehicle = vehicle;
        this.spot = spot;
        this.entryTime = System.currentTimeMillis();
    }

    public String getTicketId() { return ticketId; }
    public Vehicle getVehicle() { return vehicle; }
    public ParkingSpot getSpot() { return spot; }
    public long getEntryTime() { return entryTime; }
    public long getExitTime() { return exitTime; }
    public void setExitTime(long exitTime) { this.exitTime = exitTime; }
    public double getFee() { return fee; }
    public void setFee(double fee) { this.fee = fee; }
    public boolean isPaid() { return isPaid; }
    public void pay() { this.isPaid = true; }
}

enum PaymentType { CASH, CARD, UPI }

abstract class Payment {
    private final String transactionId;
    protected double amount;

    protected Payment(double amount) {
        this.transactionId = "TXN-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
        this.amount = amount;
    }

    public abstract boolean process();
    public String getTransactionId() { return transactionId; }
}

class CashPayment extends Payment {
    public CashPayment(double amount) { super(amount); }
    @Override
    public boolean process() {
        System.out.printf("[Cash] Processed payment of $%.2f via transaction: %s\n", amount, getTransactionId());
        return true;
    }
}

class CardPayment extends Payment {
    public CardPayment(double amount) { super(amount); }
    @Override
    public boolean process() {
        System.out.printf("[Card] Processed payment of $%.2f via transaction: %s\n", amount, getTransactionId());
        return true;
    }
}

class UPIPayment extends Payment {
    public UPIPayment(double amount) { super(amount); }
    @Override
    public boolean process() {
        System.out.printf("[UPI] Processed payment of $%.2f via transaction: %s\n", amount, getTransactionId());
        return true;
    }
}

class PaymentFactory {
    public static Payment createPayment(PaymentType type, double amount) {
        switch (type) {
            case CASH: return new CashPayment(amount);
            case CARD: return new CardPayment(amount);
            case UPI: return new UPIPayment(amount);
            default: throw new IllegalArgumentException("Unknown payment type: " + type);
        }
    }
}

class ParkingLot {
    private static volatile ParkingLot instance;
    private final String name;
    private final List<ParkingFloor> floors = new CopyOnWriteArrayList<>();
    private final ConcurrentHashMap<String, Ticket> activeTickets = new ConcurrentHashMap<>();
    private PricingStrategy pricingStrategy = new HourlyPricing(5.0);

    private ParkingLot(String name) {
        this.name = name;
    }

    public static ParkingLot getInstance(String name) {
        if (instance == null) {
            synchronized (ParkingLot.class) {
                if (instance == null) {
                    instance = new ParkingLot(name);
                }
            }
        }
        return instance;
    }

    public void addFloor(ParkingFloor floor) {
        floors.add(floor);
    }

    public void setPricingStrategy(PricingStrategy strategy) {
        this.pricingStrategy = strategy;
    }

    private boolean isSpotCompatible(VehicleType vehicleType, SpotType spotType) {
        switch (vehicleType) {
            case MOTORCYCLE: return spotType == SpotType.MOTORCYCLE || spotType == SpotType.COMPACT || spotType == SpotType.LARGE;
            case CAR: return spotType == SpotType.COMPACT || spotType == SpotType.LARGE;
            case TRUCK: return spotType == SpotType.LARGE;
            default: return false;
        }
    }

    public synchronized Ticket parkVehicle(Vehicle vehicle) {
        for (ParkingFloor floor : floors) {
            for (ParkingSpot spot : floor.getSpots()) {
                if (spot.isAvailable() && isSpotCompatible(vehicle.getType(), spot.getType())) {
                    if (spot.reserve(vehicle)) {
                        Ticket ticket = new Ticket(vehicle, spot);
                        activeTickets.put(ticket.getTicketId(), ticket);
                        System.out.printf("[ParkingSuccess] %s (%s) parked at spot %s on floor %s. Ticket: %s\n",
                                vehicle.getType(), vehicle.getLicensePlate(), spot.getId(), floor.getName(), ticket.getTicketId());
                        return ticket;
                    }
                }
            }
        }
        System.out.printf("[ParkingFailed] No compatible spot available for %s (%s)\n", vehicle.getType(), vehicle.getLicensePlate());
        return null;
    }

    public synchronized boolean checkoutVehicle(String ticketId, PaymentType paymentType) {
        Ticket ticket = activeTickets.get(ticketId);
        if (ticket == null) {
            System.out.println("[CheckoutError] Ticket ID " + ticketId + " not found.");
            return false;
        }

        ticket.setExitTime(System.currentTimeMillis() + 7200000); // Mocks 2 hours parking
        long duration = ticket.getExitTime() - ticket.getEntryTime();
        double fee = pricingStrategy.calculateFee(duration);
        ticket.setFee(fee);

        Payment payment = PaymentFactory.createPayment(paymentType, fee);
        if (payment.process()) {
            ticket.pay();
            ticket.getSpot().release();
            activeTickets.remove(ticketId);
            System.out.printf("[CheckoutSuccess] Ticket %s resolved. Fee: $%.2f. Spot %s is vacant.\n",
                    ticketId, fee, ticket.getSpot().getId());
            return true;
        }
        return false;
    }
}

public class Main {
    public static void main(String[] args) {
        ParkingLot lot = ParkingLot.getInstance("Grand Arena");
        ParkingFloor f1 = new ParkingFloor("Floor 1");
        f1.addSpot(new ParkingSpot("F1-M1", SpotType.MOTORCYCLE));
        f1.addSpot(new ParkingSpot("F1-C1", SpotType.COMPACT));
        f1.addSpot(new ParkingSpot("F1-L1", SpotType.LARGE));
        lot.addFloor(f1);

        Vehicle car1 = new Car("KA-01-AB-1234");
        Vehicle moto1 = new Motorcycle("KA-01-XYZ-567");

        Ticket t1 = lot.parkVehicle(car1);
        Ticket t2 = lot.parkVehicle(moto1);

        lot.checkoutVehicle(t1.getTicketId(), PaymentType.UPI);
        lot.checkoutVehicle(t2.getTicketId(), PaymentType.CARD);
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...