"Design a vending machine."
Press the same button twice on a vending machine — once before you insert money, once after — and you get two completely different results. Same button, same product, same customer. The only thing that changed is the machine's own internal condition. If Lesson 12 introduced State, this problem is where it's impossible to miss.
Clarifying questions
- Coins and notes both accepted, or coins only?
- Multiple product slots with different prices?
- Does the machine need to give change, and can it run out of change to give?
The states
The same select(product) call behaves completely differently depending on which state it's called from — reject or prompt for payment in IDLE, check price and either dispense or refuse in HAS_MONEY. That's the State pattern's whole reason to exist, seen with zero ambiguity.
Putting it together
class VendingState:
def insert_money(self, machine, amount): raise NotImplementedError
def select(self, machine, product): raise NotImplementedError
def dispense_done(self, machine): raise NotImplementedError
class Idle(VendingState):
def insert_money(self, machine, amount):
machine.balance += amount
machine.set_state(HasMoney())
def select(self, machine, product):
print("Insert money first")
class HasMoney(VendingState):
def insert_money(self, machine, amount):
machine.balance += amount # allow adding more before selecting
def select(self, machine, product):
if not machine.inventory.in_stock(product):
print("Out of stock — refunding")
machine.refund()
machine.set_state(Idle())
return
if machine.balance < product.price:
print("Insufficient funds")
return
change = machine.balance - product.price
if change > 0 and not machine.inventory.can_make_change(change):
print("Cannot make exact change — refunding")
machine.refund()
machine.set_state(Idle())
return
machine.inventory.dispense(product)
machine.give_change(change)
machine.balance = 0
machine.set_state(Dispensing())
class Dispensing(VendingState):
def dispense_done(self, machine):
machine.set_state(Idle())
class VendingMachine:
def __init__(self, inventory: "Inventory"):
self.inventory = inventory
self.balance = 0
self.state: VendingState = Idle()
def set_state(self, state: VendingState):
self.state = state
def insert_money(self, amount):
self.state.insert_money(self, amount)
def select(self, product):
self.state.select(self, product)
def refund(self):
print(f"Refunding ₹{self.balance}")
self.balance = 0
def give_change(self, amount):
if amount > 0:
print(f"Dispensing ₹{amount} change")Exact change and refunds — the edge case this problem is known for
inventory.can_make_change(change) before dispensing anything, and refunds cleanly if it fails — never dispense a product and then discover you can't give change back.Inventory: a separate responsibility
Stock counts and change-making logic are a different job from the transaction state machine above — bundling them into VendingMachine would be the same SRP mistake flagged since Lesson 1:
class Inventory:
def __init__(self, stock, coins):
self.stock = stock # {product: count}
self.coins = coins # {denomination: count}
def in_stock(self, product) -> bool:
return self.stock.get(product, 0) > 0
def dispense(self, product):
self.stock[product] -= 1
def can_make_change(self, amount) -> bool:
# a real implementation greedily checks available denominations
return self._greedy_make_change(amount) is not None
def _greedy_make_change(self, amount):
remaining = amount
used = {}
for denom in sorted(self.coins, reverse=True):
count = min(remaining // denom, self.coins[denom])
if count:
used[denom] = count
remaining -= denom * count
return used if remaining == 0 else NoneWhat if the interviewer changes this?
PaymentMethod interface chosen by the customer, orthogonal to the machine's own idle/has-money/dispensing condition. Notice how naming the right pattern here requires re-applying the State-vs-Strategy question from Lesson 12, not defaulting to State just because the last two lessons used it.Common mistakes
- Modeling states as booleans/flags instead of explicit state classes, letting invalid combinations sneak through.
- Dispensing a product before confirming change can actually be given.
- Bundling inventory/stock logic into the state machine itself.
- Defaulting to State for every new requirement instead of re-checking whether it's actually Strategy (like payment method selection).
Quick recap
| Class / idea | Job |
|---|---|
| VendingState (Idle, HasMoney, Dispensing) | Same method call, different behaviour per current condition. |
| VendingMachine | Holds balance + current state, delegates every action to it. |
| Inventory | Stock counts and change-making — a separate responsibility. |
| Edge case | Check change is composable before dispensing, refund cleanly if not. |
One thing to remember
The same action meaning something different depending on what state the object is currently in is the clearest possible signal for State — model each condition as its own class, don't scatter it across flags.
Practice Zone
Four MCQs, then two reasoning questions.
Which of these is NOT a natural state for a vending machine's core state machine?
Asked in


Why is 'select a product' a completely different, invalid action when the machine is Idle versus when it's in HasMoney?
Asked in

A vending machine only has ₹50 and ₹20 notes in its coin/note inventory, and a customer overpays by ₹35 that needs to be refunded as change. What should the design do?
Asked in


Why should Inventory be a separate class from the vending machine's state machine?
Asked in

Think it through, then reveal:
Trace what should happen, state by state, when a customer inserts ₹20, selects a ₹20 item, and the machine successfully dispenses it.
Asked in

A customer inserts ₹50 for a ₹30 item and expects ₹20 change, but the machine's coin inventory has no ₹20 coins or notes left (only ₹10s and ₹5s, say only 3 of each — but assume actually 2×₹10 IS enough, so pick a case where nothing sums to ₹20 exactly). What should the design do, and which class owns that decision?
Asked in


FAQ
Is a vending machine design expected to include hardware details (motors, sensors)?
No — LLD interviews want the class design and state logic, not embedded-systems hardware control. A one-line acknowledgement ("dispense() would trigger the physical mechanism, out of scope here") is sufficient.
What if the customer cancels mid-transaction?
Add a `cancel()` action valid from `HasMoney`, refunding the balance and returning to `Idle` — exactly the kind of transition the state diagram makes easy to reason about and easy to add without touching unrelated states.
Should Inventory be a Singleton?
Reasonable if there's exactly one machine with one shared stock. For a fleet of machines, each would have its own Inventory instance rather than sharing a single global one.
Last stop: a problem with no physical objects at all — just numbers, people, and who owes whom — Lesson 14: Design Splitwise →


