Back to LLD explorer
State Pattern / Behavioral• Patterns: State, Chain of Responsibility, Strategy

Vending Machine

Easy

Problem Summary

Design a commercial Vending Machine controller managing inventory slots, coin insertion balance, optimal change dispensation, and transactional mechanical dispensing states.

Functional Scope

  • Strict lifecycle states: IdleState, HasMoneyState, DispensingState, SoldOutState.
  • Accept coins/bills of various denominations ($0.25, $1.00, $5.00).
  • Item selection validation against price and shelf inventory count.
  • Optimal change calculator dispensing minimum coin count.
  • Transaction abort and refund capability.

Entity-Relationship (ER) Schema

VendingMachine [1] --> [1] VendingState
VendingMachine [1] --> [1] Inventory
Inventory [1] --> [*] ItemShelf
VendingState <|.. IdleState
VendingState <|.. HasMoneyState
VendingState <|.. DispensingState
VendingState <|.. SoldOutState

Design Approach

Use the State pattern to prevent invalid operations (such as dispensing before inserting adequate coins). Manage item shelves with price and quantity, returning unspent change via a greedy or dynamic-programming change-making algorithm.

Core Classes & Models

VendingMachine (Context managing balance, current state, and inventory)VendingState (Interface: insertMoney, selectItem, dispense, refund)IdleState, HasMoneyState, DispensingState, SoldOutStateItemShelf (Contains code, price, and current quantity)CoinDispenser (Calculates and dispenses optimal change)
Code Blueprint
123 lines
/**
 * Low-Level Design: Vending Machine
 * Design Patterns Applied:
 *  - State Pattern: Operational states (IdleState, HasMoneyState, DispenseState)
 *  - Chain of Responsibility: Cash dispensing denomination breakdown
 *  - Strategy Pattern: Dynamic shelf pricing and promotions
 */

import java.util.*;

enum Coin { NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100);
    final int value;
    Coin(int v) { this.value = v; }
}

class Item {
    private final String name;
    private final int priceCents;
    public Item(String n, int p) { this.name = n; this.priceCents = p; }
    public String getName() { return name; }
    public int getPriceCents() { return priceCents; }
}

// --- [DESIGN PATTERN: State] State interface ---
interface VendingState {
    void insertCoin(VendingMachine vm, Coin coin);
    void selectItem(VendingMachine vm, String code);
    void dispense(VendingMachine vm);
    void refund(VendingMachine vm);
}

class IdleState implements VendingState {
    @Override
    public void insertCoin(VendingMachine vm, Coin coin) {
        vm.addBalance(coin.value);
        vm.setState(vm.getHasMoneyState());
    }
    @Override public void selectItem(VendingMachine vm, String code) { System.out.println("Insert coin first."); }
    @Override public void dispense(VendingMachine vm) { System.out.println("No item selected."); }
    @Override public void refund(VendingMachine vm) { System.out.println("No balance to refund."); }
}

class HasMoneyState implements VendingState {
    @Override public void insertCoin(VendingMachine vm, Coin coin) { vm.addBalance(coin.value); }
    @Override
    public void selectItem(VendingMachine vm, String code) {
        Item item = vm.getItem(code);
        if (item == null) {
            System.out.println("Invalid code.");
            return;
        }
        if (vm.getBalance() < item.getPriceCents()) {
            System.out.println("Insufficient balance. Needed: " + item.getPriceCents() + "¢, Have: " + vm.getBalance() + "¢");
            return;
        }
        vm.setSelectedCode(code);
        vm.setState(vm.getDispenseState());
        vm.dispense();
    }
    @Override public void dispense(VendingMachine vm) { System.out.println("Select item first."); }
    @Override
    public void refund(VendingMachine vm) {
        System.out.println("Refunded: " + vm.getBalance() + "¢");
        vm.resetBalance();
        vm.setState(vm.getIdleState());
    }
}

class DispenseState implements VendingState {
    @Override public void insertCoin(VendingMachine vm, Coin coin) { System.out.println("Dispensing in progress."); }
    @Override public void selectItem(VendingMachine vm, String code) { System.out.println("Dispensing in progress."); }
    @Override
    public void dispense(VendingMachine vm) {
        Item item = vm.getItem(vm.getSelectedCode());
        vm.deductBalance(item.getPriceCents());
        System.out.println("✅ Dispensed: " + item.getName());
        int change = vm.getBalance();
        if (change > 0) System.out.println("💰 Returning change: " + change + "¢");
        vm.resetBalance();
        vm.setState(vm.getIdleState());
    }
    @Override public void refund(VendingMachine vm) { System.out.println("Cannot refund while dispensing."); }
}

class VendingMachine {
    private final Map<String, Item> inventory = new HashMap<>();
    private final VendingState idle = new IdleState();
    private final VendingState hasMoney = new HasMoneyState();
    private final VendingState dispense = new DispenseState();
    private VendingState state = idle;
    private int balance = 0;
    private String selectedCode = null;

    public void addItem(String code, Item item) { inventory.put(code, item); }
    public Item getItem(String code) { return inventory.get(code); }
    public void setState(VendingState s) { this.state = s; }
    public VendingState getIdleState() { return idle; }
    public VendingState getHasMoneyState() { return hasMoney; }
    public VendingState getDispenseState() { return dispense; }

    public void insertCoin(Coin c) { state.insertCoin(this, c); }
    public void selectItem(String code) { state.selectItem(this, code); }
    public void dispense() { state.dispense(this); }
    public void refund() { state.refund(this); }

    public void addBalance(int cents) { this.balance += cents; System.out.println("Balance: " + balance + "¢"); }
    public void deductBalance(int cents) { this.balance -= cents; }
    public void resetBalance() { this.balance = 0; }
    public int getBalance() { return balance; }
    public void setSelectedCode(String code) { this.selectedCode = code; }
    public String getSelectedCode() { return selectedCode; }
}

public class Main {
    public static void main(String[] args) {
        VendingMachine vm = new VendingMachine();
        vm.addItem("A1", new Item("Soda Can", 75));
        vm.addItem("B2", new Item("Potato Chips", 125));

        vm.insertCoin(Coin.DOLLAR);
        vm.selectItem("A1");
    }
}