Design a vending machine
Picture
A snack costs 30 and inserted credit is 50. A successful purchase decrements stock, clears credit, and returns 20. A sold-out selection must change neither stock nor credit.
Predict
Which checks must happen before committing a purchase?
Walk through the small example
The item must exist, be in stock, and be affordable. In a real machine, change availability and hardware/payment state may introduce further checks and recovery work.
Mental model and complete Python solution
Keep product prices and stock in inventory, and credit in the current session. insert adds integer units; cancel returns and clears credit. buy validates first, then updates stock and credit together in this single-threaded simulation. Rejected operations raise without spending credit. Physical dispensing is assumed reliable and exact change unlimited; real hardware would need a pending transaction and explicit recovery when payment and dispensing disagree.
What the function promises: Prices, stock, and inserted amounts use integer units. buy returns (item, change) on success. Invalid or unaffordable purchases raise ValueError and leave state unchanged. This is not a payment-processing or hardware-control implementation.
class VendingMachine:
def __init__(self, products):
if any(type(price) is not int or price <= 0
or type(stock) is not int or stock < 0
for price, stock in products.values()):
raise ValueError("Invalid price or stock")
self.products = {name: [price, stock]
for name, (price, stock) in products.items()}
self.credit = 0
def insert(self, amount):
if type(amount) is not int or amount <= 0:
raise ValueError("Insert positive integer units")
self.credit += amount
def cancel(self):
refund, self.credit = self.credit, 0
return refund
def buy(self, name):
if name not in self.products:
raise ValueError("Unknown item")
price, stock = self.products[name]
if stock == 0 or self.credit < price:
raise ValueError("Unavailable or insufficient credit")
change = self.credit - price
self.products[name][1] -= 1
self.credit = 0
return name, change
machine = VendingMachine({"snack": (30, 1)})
machine.insert(50)
assert machine.buy("snack") == ("snack", 20)
machine.insert(10)
try:
machine.buy("snack")
except ValueError:
pass
else:
raise AssertionError("Sold-out purchase succeeded")
assert machine.credit == 10 and machine.products["snack"][1] == 0
assert machine.cancel() == 10 and machine.cancel() == 0
Work and storage: Expected O(1) product lookup and transition work, O(number of products) inventory storage. Real transaction and hardware latency is outside this simulator.
When to recognize it: A failed purchase must not silently lose credit or decrease stock. Check all conditions before committing either change.
Check before applying it: This simulator assumes unlimited exact change and reliable dispensing. Real hardware needs explicit pending, failed, and reconciliation states.
Change one rule
Payment succeeds, but the motor jams. Is raising an exception enough to restore reality?
See what changes
No. A Python rollback cannot undo a physical action or an external charge automatically. Persist a transaction state, record which effects occurred, and define refund/retry/manual-reconciliation behavior.
Connect
Make legal actions visible: Legal transitions depend on current credit and inventory.
Make failure and retries part of the design: Retries must not dispense twice for the same committed purchase.