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
VendingMachine — Top-level facade exposing insert, select, cancel, and dispense; owns the current transaction state.
Inventory — Maps slot codes to (Product, count); enforces decrement-on-dispense and supports restock.
Product — Immutable product descriptor: name, price, slot affinity.
CashRegister — Tracks denomination counts; computes whether a given change amount is makeable and produces the payout.
Transaction — Per-customer session holding inserted funds and selected product; either commits to a sale or refunds.
MachineState (interface) — Idle, HasMoney, Dispensing, OutOfStock — State pattern controlling which operations are legal at each step.
ChangeMaker — Pure 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.