cconnected.
Understand the pattern. Find your own way.Find your starting point
Your learning feed/Idea 186 of 218
Ideas this builds on
Low-level design186 / 218

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.