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
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 & reliabilityHow 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 decompositionAnalyze 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 degradationQuiz
1. What pattern is used for vending machine behavior?
2. What happens when a product is sold out?
3. How is change dispensed?
4. What triggers a low stock alert?
5. Can a user insert payment before selecting a product?
Flashcards
Question
Vending machine states?
Click to reveal answer
Answer
Idle → ProductSelected → Payment → Dispensing → Dispensed → ReturnChange → Idle
Question
How to handle sold out products?
Click to reveal answer
Answer
Check inventory before selecting. If quantity is 0, reject selection with 'sold out' message.
Question
Change dispensing?
Click to reveal answer
Answer
ChangeDispenser calculates coin combination from inventory. If exact change cannot be made, transaction may be reversed.
Question
Low stock alert trigger?
Click to reveal answer
Answer
When product quantity falls below threshold (e.g., 5 units). Observer pattern notifies for restocking.
Question
Payment before selection?
Click to reveal answer
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:
- Select product (check availability)
- Insert coins/bills
- If amount >= price → Dispense
- Return change if overpaid
- 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)