SweatyImposterVending Machine

Vending Machine

Design a vending machine that holds inventory across slots, accepts coins and bills, dispenses a product, and returns correct change. Model the full transaction lifecycle including cancellation.

Clarifying questions

  • Do we accept card/contactless or just cash?
  • If we can't make exact change, do we refund or refuse the sale?
  • Can slots be restocked mid-transaction or only when idle?
  • Is there a maintenance/admin mode for restocking and cash collection?
  • Are prices fixed at config time or updatable at runtime?

Core requirements

  • Track inventory per slot with product and count.
  • Accept coins/bills incrementally and show running balance.
  • Dispense product when funds meet price and stock exists.
  • Compute and return change from available coin reserves.
  • Allow cancel-and-refund at any pre-dispense point.
  • Block sales for out-of-stock or insufficient-change states.

Canonical class breakdown

VendingMachineTop-level facade exposing insert, select, cancel, and dispense; owns the current transaction state.
InventoryMaps slot codes to (Product, count); enforces decrement-on-dispense and supports restock.
ProductImmutable product descriptor: name, price, slot affinity.
CashRegisterTracks denomination counts; computes whether a given change amount is makeable and produces the payout.
TransactionPer-customer session holding inserted funds and selected product; either commits to a sale or refunds.
MachineState (interface) — Idle, HasMoney, Dispensing, OutOfStockState pattern controlling which operations are legal at each step.
ChangeMakerPure function that, given target amount and denomination counts, returns a payout or fails.

Java sketchjava

// Java skeleton

class Product {
    private final String sku;
    private final String name;
    private final BigDecimal price;
}

class Slot {
    private final String code;
    private Product product;
    private int count;
    public boolean dispenseOne() { ... }
}

class Inventory {
    private final Map<String, Slot> slotsByCode;
    public Product peek(String code) { ... }
    public boolean dispense(String code) { ... }
    public void restock(String code, Product p, int n) { ... }
}

class CashRegister {
    private final Map<Integer, Integer> denominationCounts; // cents -> count
    public boolean canMakeChange(int amount) { ... }
    public Map<Integer, Integer> payout(int amount) { ... }
    public void accept(Map<Integer, Integer> insertedFunds) { ... }
}

class ChangeMaker {
    // Pure: given target and counts, return payout or empty
    public static Optional<Map<Integer, Integer>> make(int target, Map<Integer, Integer> counts) { ... }
}

class Transaction {
    private final Map<Integer, Integer> insertedFunds;
    private String selectedSlot;
    public int balanceCents() { ... }
    public void insert(int denomination) { ... }
    public Map<Integer, Integer> refund() { ... }
}

interface MachineState {
    void insertMoney(VendingMachine m, int denom);
    void selectProduct(VendingMachine m, String code);
    void cancel(VendingMachine m);
    void dispense(VendingMachine m);
}

class IdleState implements MachineState { /* ... */ }
class HasMoneyState implements MachineState { /* ... */ }
class DispensingState implements MachineState { /* ... */ }
class OutOfStockState implements MachineState { /* ... */ }

class VendingMachine {
    private final Inventory inventory;
    private final CashRegister register;
    private Transaction current;
    private MachineState state;
    public void insertMoney(int denom) { state.insertMoney(this, denom); }
    public void selectProduct(String code) { state.selectProduct(this, code); }
    public void cancel() { state.cancel(this); }
    public void dispense() { state.dispense(this); }
}

Key decisions to defend

  • State pattern for the machine, not a switch on a status enum — guards against illegal operations cleanly.
  • ChangeMaker is a pure component, not a method on CashRegister, so you can unit-test the greedy/DP logic in isolation.
  • Transaction owns inserted funds; VendingMachine does not, so cancel is just 'discard transaction'.
  • Price lives on Product, not Slot — the same product in two slots shouldn't drift.
  • Refuse the sale (and refund) when exact change can't be made; never short-change the customer.

Likely follow-ups

  • · Add card payments — how does Transaction change?
  • · Support promotions (buy-one-get-one) without rewriting checkout.
  • · Concurrent kiosks share a CashRegister — what now?

Dive deeper

Vending machine is a classic State pattern exercise. The interviewer is watching for whether you reach for State to model legal transitions, or whether you bury them in if-statements that will rot. The change-making sub-problem also tests whether you separate algorithms from state — strong candidates extract ChangeMaker so they can swap greedy for DP when coin denominations stop being canonical.

All OOD prompts