LLD Problem
Design Vending Machine
Design a vending machine using the State pattern. Handle coin insertion, product selection, dispensing, and change return.
20 min readState Pattern Example
1Why State Pattern?
Vending machine behavior changes based on its state: Idle, HasMoney,Dispensing. Each state handles actions differently. This is a perfect use case for the State Pattern.
Vending Machine States
2Class Design
VendingMachine
state
inventory
balance
selectedProduct
insertCoin()
selectProduct()
dispense()
refund()
VendingState
Interface
insertCoin()
selectProduct()
dispense()
refund()
IdleState
Implements VendingState
Only insertCoin() works
HasMoneyState
Implements VendingState
Can select, refund, add more
DispensingState
Implements VendingState
Dispenses, returns to Idle
Product
id
name
price
quantity
getPrice()
decrementQty()
3Implementation
Vending Machine with State Pattern
// ========== Product ==========
class Product {
private String id;
private String name;
private double price;
private int quantity;
public Product(String id, String name, double price, int qty) {
this.id = id; this.name = name;
this.price = price; this.quantity = qty;
}
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public void decrementQuantity() { quantity--; }
public String getName() { return name; }
public String getId() { return id; }
}
// ========== State Interface ==========
interface VendingState {
void insertCoin(VendingMachine vm, double amount);
void selectProduct(VendingMachine vm, String productId);
void dispense(VendingMachine vm);
void refund(VendingMachine vm);
}
// ========== Idle State ==========
class IdleState implements VendingState {
@Override
public void insertCoin(VendingMachine vm, double amount) {
vm.addBalance(amount);
System.out.println("Inserted: $" + amount + ". Balance: $" + vm.getBalance());
vm.setState(new HasMoneyState());
}
@Override
public void selectProduct(VendingMachine vm, String id) {
System.out.println("Please insert coins first.");
}
@Override
public void dispense(VendingMachine vm) {
System.out.println("Nothing to dispense.");
}
@Override
public void refund(VendingMachine vm) {
System.out.println("No balance to refund.");
}
}
// ========== Has Money State ==========
class HasMoneyState implements VendingState {
@Override
public void insertCoin(VendingMachine vm, double amount) {
vm.addBalance(amount);
System.out.println("Added: $" + amount + ". Balance: $" + vm.getBalance());
}
@Override
public void selectProduct(VendingMachine vm, String id) {
Product product = vm.getProduct(id);
if (product == null) {
System.out.println("Invalid product.");
return;
}
if (product.getQuantity() == 0) {
System.out.println(product.getName() + " is out of stock.");
return;
}
if (vm.getBalance() < product.getPrice()) {
System.out.println("Insufficient balance. Need $" +
(product.getPrice() - vm.getBalance()) + " more.");
return;
}
vm.setSelectedProduct(product);
vm.setState(new DispensingState());
vm.dispense();
}
@Override
public void dispense(VendingMachine vm) {
System.out.println("Please select a product first.");
}
@Override
public void refund(VendingMachine vm) {
System.out.println("Refunding: $" + vm.getBalance());
vm.resetBalance();
vm.setState(new IdleState());
}
}
// ========== Dispensing State ==========
class DispensingState implements VendingState {
@Override
public void insertCoin(VendingMachine vm, double amount) {
System.out.println("Please wait, dispensing...");
}
@Override
public void selectProduct(VendingMachine vm, String id) {
System.out.println("Please wait, dispensing...");
}
@Override
public void dispense(VendingMachine vm) {
Product product = vm.getSelectedProduct();
double change = vm.getBalance() - product.getPrice();
product.decrementQuantity();
System.out.println("Dispensing: " + product.getName());
if (change > 0) {
System.out.println("Change: $" + change);
}
vm.resetBalance();
vm.setSelectedProduct(null);
vm.setState(new IdleState());
}
@Override
public void refund(VendingMachine vm) {
System.out.println("Cannot refund during dispensing.");
}
}
// ========== Vending Machine ==========
class VendingMachine {
private VendingState state;
private Map<String, Product> inventory;
private double balance;
private Product selectedProduct;
public VendingMachine() {
this.state = new IdleState();
this.inventory = new HashMap<>();
this.balance = 0;
}
public void addProduct(Product product) {
inventory.put(product.getId(), product);
}
// Delegated to current state
public void insertCoin(double amount) { state.insertCoin(this, amount); }
public void selectProduct(String id) { state.selectProduct(this, id); }
public void dispense() { state.dispense(this); }
public void refund() { state.refund(this); }
// Helpers for states
public void setState(VendingState s) { this.state = s; }
public void addBalance(double amt) { this.balance += amt; }
public void resetBalance() { this.balance = 0; }
public double getBalance() { return balance; }
public Product getProduct(String id) { return inventory.get(id); }
public void setSelectedProduct(Product p) { this.selectedProduct = p; }
public Product getSelectedProduct() { return selectedProduct; }
}
// ========== Usage ==========
public class Main {
public static void main(String[] args) {
VendingMachine vm = new VendingMachine();
vm.addProduct(new Product("A1", "Coke", 1.50, 10));
vm.addProduct(new Product("A2", "Chips", 2.00, 5));
vm.selectProduct("A1"); // Error: insert coins first
vm.insertCoin(1.00); // Balance: $1.00
vm.insertCoin(1.00); // Balance: $2.00
vm.selectProduct("A1"); // Dispenses Coke, $0.50 change
}
}4Interview Follow-up Questions
Interview Follow-up Questions
Common follow-up questions interviewers ask
5Key Takeaways
1State Pattern is perfect for vending machine.
2States: Idle, HasMoney, Dispensing.
3Each state handles all actions and controls transitions.
4Machine delegates to current state - no if/else chains.
5Easy to add new states (e.g., Maintenance, OutOfStock).
6Consider concurrency and timeout scenarios.