Bird
Raised Fist0

Design a Vending Machine - States, Product Selection, Payment, Change, Refill

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
🏗️
Design a Vending Machine - States, Product Selection, Payment, Change, Refill
Design a vending machine with clear state transitions, product selection, payment handling, change dispensing, and refill capabilities using Java-specific concurrency and OOP principles.
mediumSystems with State MachinesFreq: medium
Prerequisites:OOP BasicsFinite State Machines
⚙️
Functional Requirements
  • Allow product selection from available items
  • Accept multiple payment methods (coins, bills, card)
  • Dispense product only after full payment
  • Return correct change if overpaid
  • Support refilling products and cash collection by maintenance
  • Handle out-of-stock and insufficient payment gracefully
  • Maintain internal state transitions to prevent invalid operations
👤
Use Cases
  • Customer selects a product
  • Customer inserts payment
  • Machine dispenses product and change
  • Maintenance staff refills products and collects cash
  • Machine handles out-of-stock and insufficient payment scenarios
🧑‍💻
Actors
  • Customer
  • Maintenance Staff
  • Payment Gateway
❓
Clarifying Questions to Ask
  • How many concurrent users or threads can interact with the machine simultaneously?
  • Is the pricing fixed per product or dynamic?
  • What happens if the machine runs out of change?
  • Are partial payments allowed or must payment be completed in one go?
  • Should the machine support refunds or cancellations mid-transaction?
Out of Scope
✗ Networked remote monitoring
✗ Dynamic pricing or promotions
✗ User authentication
⚡
QPS Calculation
expected qps10
rationaleAssuming up to 10 transactions per second based on typical vending machine usage in busy locations.
🖥️
Server Estimate
count1
rationaleSingle embedded JVM instance per vending machine; no distributed servers required.
🔢
Enumerations
enum VendingMachineState
IDLE | SELECTING | PAYING | DISPENSING | OUT_OF_SERVICE | REFILLING
Enum prevents typo bugs and enables exhaustive switch statements for state transitions, ensuring all states are handled explicitly.
enum PaymentMethod
COIN | BILL | CARD
Defines allowed payment types explicitly, preventing invalid payment method usage and enabling polymorphic payment processing.
enum ProductCategory
SNACK | DRINK | CANDY
Categorizes products for potential filtering or pricing strategies, enabling extensibility without modifying product class.
enum TransactionStatus
PENDING | COMPLETED | FAILED | CANCELLED
Tracks payment transaction lifecycle, enabling clear state management and error handling.
📦
VendingMachine
5 fields
private volatile VendingMachineState currentStateprivate Map<String, ProductSlot> productSlotsprivate PaymentProcessor paymentProcessorprivate ChangeDispenser changeDispenserprivate final Object stateLock
Manages overall vending machine state, coordinates product selection, payment processing, dispensing, and refilling operations with thread-safe state transitions.
Why thisUses Java synchronized methods and volatile fields to ensure thread safety in concurrent environments, suitable for embedded systems without external frameworks.
Why notAvoids using external concurrency libraries like java.util.concurrent.locks to keep design simple and compatible with constrained embedded JVMs.
public synchronized VendingMachineState getCurrentState()Returns the current state of the vending machine in a thread-safe manner.
public synchronized boolean selectProduct(String productCode)Allows customer to select a product if in IDLE or SELECTING state; transitions state accordingly.
public synchronized boolean insertPayment(PaymentMethod method, double amount)Accepts payment input, updates payment status, and transitions state to PAYING or DISPENSING.
public synchronized boolean dispenseProduct()Dispenses the selected product and change if payment is sufficient; transitions state to IDLE.
public synchronized void refillProductSlot(String productCode, int quantity)Allows maintenance to refill a product slot; only allowed in REFILLING or OUT_OF_SERVICE states.
public synchronized double collectCash()Allows maintenance to collect accumulated cash safely.
📦
ProductSlot
3 fields
private final String productCodeprivate final Product productprivate volatile int quantity
Holds product details and current quantity; manages inventory count atomically.
Why thisUses synchronized methods to ensure atomic updates to quantity, suitable for concurrent access in vending machine environment.
Why notAvoids using AtomicInteger to reduce complexity and dependency on java.util.concurrent.atomic package.
public synchronized boolean decrementQuantity()Atomically decrements quantity if available; returns false if out of stock.
public synchronized void refill(int amount)Increases quantity by specified amount.
public synchronized int getQuantity()Returns current quantity in a thread-safe manner.
📦
Product
4 fields
private final String codeprivate final String nameprivate final double priceprivate final ProductCategory category
Represents immutable product details; no setters to enforce immutability.
Why thisImmutable class ensures thread safety without synchronization, suitable for shared product data.
Why notAvoids mutable product objects to prevent synchronization overhead and data inconsistency.
public double getPrice()Returns product price.
public String getName()Returns product name.
📦
PaymentProcessor
2 fields
private volatile double amountPaidprivate volatile TransactionStatus transactionStatus
Handles payment acceptance, validation, and transaction status updates with thread-safe methods.
Why thisSynchronized methods ensure consistent updates to payment state in concurrent scenarios.
Why notAvoids external payment libraries to keep design self-contained and focused on core logic.
public synchronized boolean acceptPayment(PaymentMethod method, double amount, double price)Accepts payment amount and updates transaction status; returns true if payment covers price.
public synchronized double getChange(double price)Calculates and returns change amount if overpaid.
public synchronized void resetTransaction()Resets payment state for new transaction.
📦
ChangeDispenser
1 fields
private Map<Double, Integer> coinInventory
Manages coin inventory and dispenses correct change using synchronized methods to prevent race conditions.
Why thisSynchronized methods ensure atomic updates to coin inventory and correct change dispensing.
Why notAvoids complex concurrency utilities to maintain simplicity and reliability in embedded environment.
public synchronized Map<Double, Integer> dispenseChange(double amount)Calculates and dispenses change coins; updates inventory atomically.
public synchronized void refillCoins(Map<Double, Integer> coins)Refills coin inventory.
📦
MaintenancePanel
1 fields
private final VendingMachine vendingMachine
Provides interface for maintenance staff to refill products, collect cash, and perform diagnostics.
Why thisEncapsulates maintenance operations separately to enforce access control and separation of concerns.
Why notAvoids exposing maintenance methods directly on VendingMachine to prevent unauthorized access.
public synchronized void refillProduct(String productCode, int quantity)Refills specified product slot.
public synchronized double collectCash()Collects accumulated cash from machine.
public synchronized void setOutOfService(boolean status)Sets machine state to OUT_OF_SERVICE or back.
🗄️
ProductInventory
PK: productCode
productCode STRINGslotId INTquantity INTlastRefilled TIMESTAMP
Stores inventory levels per product slot.
🗄️
TransactionLog
PK: transactionId
transactionId UUIDproductCode STRINGamountPaid DOUBLEchangeReturned DOUBLEstatus STRINGtimestamp TIMESTAMP
Logs payment transactions for auditing and troubleshooting.
🔌
selectProduct
Request
{
  "productCode": "string"
}
Response
{
  "success": "boolean",
  "message": "string"
}
Allows customer to select a product if available.
🔌
insertPayment
Request
{
  "paymentMethod": "string",
  "amount": "double"
}
Response
{
  "success": "boolean",
  "message": "string",
  "changeDue": "double"
}
Accepts payment and returns change if overpaid.
🔌
refillProduct
Request
{
  "productCode": "string",
  "quantity": "int"
}
Response
{
  "success": "boolean",
  "message": "string"
}
Allows maintenance staff to refill product slots.

Practice

(1/5)
1. In the in-memory file system design, a FileSystem class has a private constructor and a static synchronized getInstance() method returning the single instance. Which pattern is applied here and why is this the correct use case?
easy
A. Factory pattern, because it controls object creation centrally.
B. Singleton pattern, to ensure only one instance manages the entire file system state.
C. Builder pattern, to construct complex file system objects step-by-step.
D. Prototype pattern, to clone existing file system 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 shared instance, matching Singleton pattern characteristics.
  2. Step 2: Reason why Singleton fits this use case

    Singleton is appropriate because the file system state must be globally consistent and unique; multiple instances would cause conflicting states.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Private constructor + static synchronized getInstance() -> Singleton [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
2. In designing the Order class, should the OrderState be an abstract class or an interface? Consider that each state has different behavior but shares no common state variables. Which is the best choice and why?
easy
A. Abstract class, because it allows sharing common state variables and default method implementations.
B. Interface, because states only define behavior contracts without shared state, enabling multiple inheritance.
C. Abstract class, because it enforces a fixed inheritance hierarchy and prevents multiple implementations.
D. Interface, because it allows storing state variables in the interface for reuse.

Solution

  1. Step 1: Analyze state sharing needs

    States have no shared variables, only behavior differences, so no need for shared state in a superclass.
  2. Step 2: Evaluate interface vs abstract class

    Interface suits behavior contracts without state and allows flexibility; abstract class is unnecessary and restrictive here.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Interface fits stateless behavior contracts best [check]
Hint: Interface for behavior-only contracts without shared state [check]
Common Mistakes:
3. The Ride Booking system wants to add a WeekendFeeStrategy for fare calculation. Given the current design uses a FeeStrategy interface, what is the minimum change required to comply with SOLID principles?
medium
A. Modify the existing FareCalculator class to add weekend fee logic inside its calculateFare() method.
B. Add a boolean flag isWeekend to FareCalculator and branch logic accordingly.
C. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes.
D. Create a WeekendFareCalculator subclass extending FareCalculator and override calculateFare().

Solution

  1. Step 1: Identify applicable SOLID principle

    The Open-Closed Principle (OCP) requires classes to be open for extension but closed for modification.
  2. Step 2: Evaluate options against OCP

    Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes. adds a new FeeStrategy implementation without modifying existing classes, adhering to OCP. Options A and C modify existing classes, violating OCP. Create a WeekendFareCalculator subclass extending FareCalculator and override calculateFare(). creates subclass but may cause class explosion.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    New FeeStrategy implementation -> OCP compliant [check]
Hint: New strategy class, no existing class changes [check]
Common Mistakes:
4. In a parking lot system, the parkVehicle() method checks if a spot is available and then marks it occupied. Without synchronized, which exact thread interleaving causes double-booking?
medium
A. Thread 1 reads spot as occupied, Thread 2 reads spot as available, both park vehicles.
B. Thread 1 marks spot occupied, Thread 2 waits and then parks after spot is freed.
C. Thread 1 checks isAvailable() -> true, context switch before marking occupied; Thread 2 checks isAvailable() -> true, both mark occupied.
D. Thread 1 and Thread 2 both wait on a lock, preventing double-booking.

Solution

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

    Without synchronization, Thread 1 reads spot as available but is preempted before marking it occupied. Thread 2 also reads spot as available and proceeds to mark it occupied.
  2. Step 2: Understand resulting double-booking

    Both threads believe the spot is free and park vehicles, causing double-booking due to the gap between check and update.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Check-then-act gap without synchronization -> double-booking [check]
Hint: Check-then-act gap -> both see available -> double-book [check]
Common Mistakes:
5. The system needs to support ElectricCar and ChargingSpot without modifying existing classes. What is the minimum change to the current Java design to add these features while preserving extensibility?
hard
A. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility.COMPATIBILITY_MAP accordingly.
B. Modify Vehicle class to include a boolean isElectric field and add charging logic inside existing ParkingSpot class.
C. Create a new ElectricCarChargingManager class that extends BookingManager and handles charging spots separately.
D. Add a new interface Chargeable and modify all Vehicle subclasses to implement it, including ElectricCar.

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Adding new enums or modifying compatibility maps changes existing classes, violating OCP.
  2. Step 2: Use composition by adding an isElectric field and charging logic in ParkingSpot to handle charging without class explosion.

    This approach extends functionality with minimal changes and avoids class explosion or god classes.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Use fields and composition, avoid modifying enums or compatibility maps [check]
Hint: Add fields and logic, avoid modifying enums or compatibility maps [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