Skip to content
intermediatePhase 50 · LLD Practice

Vending Machine

Design a vending machine with inventory, selection, and payment.

1h 30m
0 problems
Topic Progress0%

Requirements

Functional Requirements

1. Product Display:
   - Show available products
   - Show prices
   - Show product images

2. Selection:
   - Select product by code
   - Support multiple products

3. Payment:
   - Insert coins (nickel, dime, quarter)
   - Insert bills ($1, $5, $10)
   - Display current balance

4. Dispensing:
   - Dispense selected product
   - Return change
   - Handle sold out

5. Inventory:
   - Track stock levels
   - Low stock alerts

Vending Machine States

┌─────────────────────────────────────────────┐
│            Vending Machine States            │
├─────────────────────────────────────────────┤
│                                              │
│  IDLE → PRODUCT_SELECTED → PAYMENT           │
│    │                         │               │
│    │                         ▼               │
│    │                    DISPENSING           │
│    │                         │               │
│    │                         ▼               │
│    │                    DISPENSED            │
│    │                         │               │
│    │                         ▼               │
│    │                    RETURNING_CHANGE     │
│    │                         │               │
│    └─────────────────────────┘               │
│                                              │
└─────────────────────────────────────────────┘

Core Entities

VendingMachine, Product, Inventory,
Coin, Bill, Payment, ChangeDispenser

State Pattern

State Interface

public interface VendingMachineState {
    void selectProduct(VendingMachine machine, String code);
    void insertCoin(VendingMachine machine, Coin coin);
    void insertBill(VendingMachine machine, Bill bill);
    void dispense(VendingMachine machine);
    void returnChange(VendingMachine machine);
}

Idle State

public class IdleState implements VendingMachineState {
    @Override
    public void selectProduct(VendingMachine machine, String code) {
        Product product = machine.getInventory().getProduct(code);
        if (product == null) {
            System.out.println("Invalid selection.");
            return;
        }
        if (product.getQuantity() == 0) {
            System.out.println("Product sold out.");
            return;
        }
        machine.setSelectedProduct(product);
        machine.setState(new ProductSelectedState());
        System.out.println("Selected: " + product.getName() + 
                          " - $" + product.getPrice());
    }
    
    @Override
    public void insertCoin(VendingMachine machine, Coin coin) {
        System.out.println("Please select a product first.");
    }
}

ProductSelected State

public class ProductSelectedState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine machine, Coin coin) {
        machine.addPayment(coin.getValue());
        System.out.println("Inserted: $" + coin.getValue() + 
                          " | Total: $" + machine.getCurrentPayment());
        
        if (machine.getCurrentPayment() >= 
            machine.getSelectedProduct().getPrice()) {
            machine.setState(new DispensingState());
        }
    }
    
    @Override
    public void insertBill(VendingMachine machine, Bill bill) {
        machine.addPayment(bill.getValue());
        System.out.println("Inserted: $" + bill.getValue() + 
                          " | Total: $" + machine.getCurrentPayment());
        
        if (machine.getCurrentPayment() >= 
            machine.getSelectedProduct().getPrice()) {
            machine.setState(new DispensingState());
        }
    }
    
    @Override
    public void returnChange(VendingMachine machine) {
        machine.returnPayment();
        machine.setSelectedProduct(null);
        machine.setState(new IdleState());
    }
}

Dispensing State

public class DispensingState implements VendingMachineState {
    @Override
    public void dispense(VendingMachine machine) {
        Product product = machine.getSelectedProduct();
        
        // 1. Dispense product
        machine.getInventory().dispense(product.getCode());
        System.out.println("Dispensing: " + product.getName());
        
        // 2. Calculate change
        double change = machine.getCurrentPayment() - product.getPrice();
        if (change > 0) {
            machine.getChangeDispenser().dispense(change);
            System.out.println("Change: $" + change);
        }
        
        // 3. Reset
        machine.setSelectedProduct(null);
        machine.resetPayment();
        machine.setState(new IdleState());
        System.out.println("Thank you!");
    }
}

Inventory

Inventory Management

public class Inventory {
    private final Map<String, Product> products;
    private final int lowStockThreshold;
    
    public Inventory(int lowStockThreshold) {
        this.products = new HashMap<>();
        this.lowStockThreshold = lowStockThreshold;
    }
    
    public void addProduct(String code, String name, 
                          double price, int quantity) {
        products.put(code, new Product(code, name, price, quantity));
    }
    
    public Product getProduct(String code) {
        return products.get(code);
    }
    
    public boolean isAvailable(String code) {
        Product p = products.get(code);
        return p != null && p.getQuantity() > 0;
    }
    
    public void dispense(String code) {
        Product p = products.get(code);
        if (p != null && p.getQuantity() > 0) {
            p.decrementQuantity();
            if (p.getQuantity() <= lowStockThreshold) {
                notifyLowStock(p);
            }
        }
    }
    
    private void notifyLowStock(Product product) {
        // Observer pattern: notify for restocking
    }
}

Product Class

public class Product {
    private final String code;
    private final String name;
    private final double price;
    private int quantity;
    
    public void decrementQuantity() {
        if (quantity > 0) {
            quantity--;
        }
    }
}

Change Dispenser

public class ChangeDispenser {
    private final Map<Coin, Integer> coinInventory;
    
    public Map<Coin, Integer> dispense(double amount) {
        Map<Coin, Integer> change = new HashMap<>();
        double remaining = amount;
        
        for (Coin coin : Coin.values()) {
            while (remaining >= coin.getValue() && 
                   coinInventory.get(coin) > 0) {
                change.merge(coin, 1, Integer::sum);
                coinInventory.merge(coin, -1, Integer::sum);
                remaining -= coin.getValue();
            }
        }
        
        if (remaining > 0) {
            // Cannot dispense exact change
            rollback(change);
            throw new InsufficientChangeException();
        }
        
        return change;
    }
}

Denominations

public enum Coin {
    NICKEL(0.05), DIME(0.10), QUARTER(0.25);
    
    private final double value;
}

public enum Bill {
    ONE(1.00), FIVE(5.00), TEN(10.00);
    
    private final double value;
}

Follow-ups

Follow-up Questions

1. How to handle cashless payment?
   → Add CardPaymentState
   → Integrate with payment terminal
   → Support NFC/QR code

2. How to handle product with multiple options?
   → Product variants (size, flavor)
   → Option selection state

3. How to handle refund on jammed product?
   → RefundState
   → Sensor for product delivery
   → Automatic refund if not dispensed

4. How to handle multiple vending machines?
   → Central inventory management
   → Remote monitoring
   → Automatic restocking alerts

5. How to handle temperature-controlled products?
   → Temperature monitoring
   → Separate compartment
   → Different pricing

Design Patterns

Pattern Usage
State Machine states
Strategy Payment processing
Observer Low stock alerts
Factory Product creation

Display Board

┌──────────────────────────────────────┐
│         VENDING MACHINE              │
├──────────────────────────────────────┤
│  A1: Cola      $1.50  [Stock: 10]   │
│  A2: Water     $1.00  [Stock: 15]   │
│  B1: Chips     $2.00  [Stock: 5]    │
│  B2: Candy     $1.25  [Stock: 20]   │
├──────────────────────────────────────┤
│  Balance: $0.00                      │
│  Insert coins or bills               │
└──────────────────────────────────────┘

Practice Problems

0/3solved
Design Vending Machine System

Design a scalable Vending Machine system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Vending Machine Scaling

How would you scale Vending Machine to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Vending Machine Failure Modes

Analyze potential failure modes for Vending Machine and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What pattern is used for vending machine behavior?

Question 1 options

2. What happens when a product is sold out?

Question 2 options

3. How is change dispensed?

Question 3 options

4. What triggers a low stock alert?

Question 4 options

5. Can a user insert payment before selecting a product?

Question 5 options

Flashcards

Question

Vending machine states?

Answer

Idle → ProductSelected → Payment → Dispensing → Dispensed → ReturnChange → Idle

Question

How to handle sold out products?

Answer

Check inventory before selecting. If quantity is 0, reject selection with 'sold out' message.

Question

Change dispensing?

Answer

ChangeDispenser calculates coin combination from inventory. If exact change cannot be made, transaction may be reversed.

Question

Low stock alert trigger?

Answer

When product quantity falls below threshold (e.g., 5 units). Observer pattern notifies for restocking.

Question

Payment before selection?

Answer

In standard design: no. IdleState rejects payment. Product must be selected first to determine price.

Revision Notes

Key Takeaways

  • 1.State pattern manages vending machine states and allowed operations
  • 2.Product selection is validated against inventory before payment
  • 3.Change dispenser calculates optimal coin combination
  • 4.Low stock alerts trigger restocking notifications
  • 5.Sold out products are rejected at selection time

Interview Tips

  • Show state transitions clearly
  • Explain how change is calculated and dispensed
  • Discuss inventory management and low stock handling
  • Mention cashless payment as a follow-up extension

Cheat Sheet

Vending Machine - Cheat Sheet

States:
Idle → ProductSelected → Dispensing → ReturnChange

Work Flow:

  1. Select product (check availability)
  2. Insert coins/bills
  3. If amount >= price → Dispense
  4. Return change if overpaid
  5. Return to Idle

Inventory:

  • Track quantity per product
  • Low stock threshold alerts
  • Dispense decrements quantity

Change:

  • Calculate from coin inventory
  • Use largest coins first
  • Rollback if can't make change

Patterns:
State (machine states), Strategy (payment), Observer (stock alerts)