LESSON 11.3
Walkthrough: vending machine
Design and code a robust Vending Machine using the State pattern to model coin insertion, product dispensing, change return, and out-of-stock conditions.
6 min read
The Vending Machine is a classic LLD problem designed to test whether a candidate can model a finite state machine using clean object-oriented patterns instead of messy conditional flags.
1. Requirements
- Inventory: Multiple shelves/racks holding items with a price and quantity.
- Payment: Accept coins/cash, track accumulated balance, calculate and return change.
- States:
IdleState(No money inserted yet).HasMoneyState(Coins entered, awaiting selection or refund).DispensingState(Item approved, product dropping, change returned).SoldOutState(Selected item empty).
2. State Transition Diagram
Laying out the diagram…
3. Implementation
// State Interface
class VendingState {
insertCoin(machine, amount) { throw new Error("Action not permitted in this state"); }
selectItem(machine, code) { throw new Error("Action not permitted in this state"); }
dispense(machine) { throw new Error("Action not permitted in this state"); }
refund(machine) { throw new Error("Action not permitted in this state"); }
}
// 1. Idle State
class IdleState extends VendingState {
insertCoin(machine, amount) {
machine.addBalance(amount);
machine.setState(new HasMoneyState());
return `Accepted $${amount}. Total balance: $${machine.balance}`;
}
}
// 2. HasMoney State
class HasMoneyState extends VendingState {
insertCoin(machine, amount) {
machine.addBalance(amount);
return `Added $${amount}. Total: $${machine.balance}`;
}
selectItem(machine, code) {
const item = machine.getItem(code);
if (!item || item.quantity <= 0) {
return "Item out of stock";
}
if (machine.balance < item.price) {
return `Insufficient funds. Price is $${item.price}, balance is $${machine.balance}`;
}
machine.setSelectedItem(item);
machine.setState(new DispensingState());
return machine.dispense();
}
refund(machine) {
const refunded = machine.balance;
machine.resetBalance();
machine.setState(new IdleState());
return `Refunded $${refunded}`;
}
}
// 3. Dispensing State
class DispensingState extends VendingState {
dispense(machine) {
const item = machine.selectedItem;
item.quantity--;
const change = machine.balance - item.price;
machine.resetBalance();
machine.setState(new IdleState());
return `Dispensed ${item.name}! Change returned: $${change.toFixed(2)}`;
}
}
// Machine Context
class VendingMachine {
#inventory = new Map();
#balance = 0;
#state = new IdleState();
selectedItem = null;
addItem(code, name, price, quantity) {
this.#inventory.set(code, { name, price, quantity });
}
getItem(code) { return this.#inventory.get(code); }
setState(state) { this.#state = state; }
addBalance(amount) { this.#balance += amount; }
resetBalance() { this.#balance = 0; this.selectedItem = null; }
get balance() { return this.#balance; }
get stateName() { return this.#state.constructor.name; }
setSelectedItem(item) { this.selectedItem = item; }
insertCoin(amount) { return this.#state.insertCoin(this, amount); }
selectItem(code) { return this.#state.selectItem(this, code); }
dispense() { return this.#state.dispense(this); }
refund() { return this.#state.refund(this); }
}
// Verification Test
const vm = new VendingMachine();
vm.addItem("A1", "Soda", 1.50, 3);
// 1. Insert coin: Idle -> HasMoney
vm.insertCoin(1.00);
vm.insertCoin(1.00);
console.assert(vm.balance === 2.00);
console.assert(vm.stateName === "HasMoneyState");
// 2. Select item: HasMoney -> Dispensing -> Idle
const receipt = vm.selectItem("A1");
console.assert(receipt.includes("Dispensed Soda"));
console.assert(receipt.includes("Change returned: $0.50"));
console.assert(vm.stateName === "IdleState");
console.assert(vm.balance === 0);
console.log(receipt);public class Example {
static final class Item {
final String name;
final double price;
int quantity;
Item(String name, double price, int qty) { this.name = name; this.price = price; this.quantity = qty; }
}
JavaScript · 100 lines
UTF-8Edge Cases to Discuss
- What if the machine has insufficient change coins to return the exact difference?
- How to handle transaction timeouts (if a user inserts $1.00 and walks away without choosing an item)?
- How to replenish inventory in Maintenance mode?
Try it
Vending Machine LLD
Extend the Vending Machine to track coin denominations in a cash cassette and reject invalid change requests.