Skip to main content

Vending Machine LLD2026-05-14

Deep Dive: Vending Machine Problem (State Pattern)

This low-level design models a vending machine with clear state transitions:

  • NoMoneyState: Waiting for user input and money insertion.
  • HasMoneyState: User can insert more money, eject money, or select a product.
  • DispenseState: Machine dispenses a product and updates balance and stock.
  • SoldOutState: Machine blocks operations when all products are out of stock.

The machine delegates behavior to the active state object, keeping each state focused and avoiding large conditional blocks.

Full Implementation

from abc import ABC, abstractmethod


class State(ABC):
@abstractmethod
def insert_money(self, amount):
pass

@abstractmethod
def eject_money(self):
pass

@abstractmethod
def select_product(self, name):
pass

@abstractmethod
def dispense_product(self, name):
pass


class SoldOutState(State):
def __init__(self, machine):
self.machine = machine

def insert_money(self, amount):
print("Machine is sold out.")

def eject_money(self):
print("Machine is sold out.")

def select_product(self, name):
print("Machine is sold out.")

def dispense_product(self, name):
print("Machine is sold out.")


class NoMoneyState(State):
def __init__(self, machine):
self.machine = machine

def insert_money(self, amount):
self.machine.balance += amount
print(f"Inserted: {amount}. Current balance: {self.machine.balance}")
self.machine.set_state(self.machine.has_money_state)

def eject_money(self):
print("No money to return.")

def select_product(self, name):
print("Insert money first.")

def dispense_product(self, name):
print("Select a product first.")


class HasMoneyState(State):
def __init__(self, machine):
self.machine = machine

def insert_money(self, amount):
self.machine.balance += amount
print(f"Balance updated: {self.machine.balance}")

def eject_money(self):
print(f"Returned: {self.machine.balance}")
self.machine.balance = 0

if self.machine.is_out_of_stock():
self.machine.set_state(self.machine.sold_out_state)
return

self.machine.set_state(self.machine.no_money_state)

def select_product(self, name):
if name not in self.machine.items:
print("Invalid product.")
return

price, quantity = self.machine.items[name]
if quantity <= 0:
print("Product out of stock.")
return

if self.machine.balance < price:
print(f"Insufficient balance. Add {price - self.machine.balance} more.")
return

self.machine.set_state(self.machine.dispense_state)
self.machine.dispense_product(name)

def dispense_product(self, name):
print("Select a product before dispensing.")


class DispenseState(State):
def __init__(self, machine):
self.machine = machine

def insert_money(self, amount):
print("Dispensing in progress. Please wait.")

def eject_money(self):
print("Cannot eject money while dispensing.")

def select_product(self, name):
print("Already dispensing a product.")

def dispense_product(self, name):
price, quantity = self.machine.items[name]
self.machine.items[name][1] = quantity - 1
self.machine.balance -= price

print(f"Dispensed: {name}")
print(f"Remaining balance: {self.machine.balance}")

if self.machine.is_out_of_stock():
self.machine.set_state(self.machine.sold_out_state)
return

if self.machine.balance > 0:
self.machine.set_state(self.machine.has_money_state)
else:
self.machine.set_state(self.machine.no_money_state)


class VendingMachine:
def __init__(self, items):
self.no_money_state = NoMoneyState(self)
self.has_money_state = HasMoneyState(self)
self.dispense_state = DispenseState(self)
self.sold_out_state = SoldOutState(self)

self.items = items
self.balance = 0

if self.is_out_of_stock():
self.current_state = self.sold_out_state
else:
self.current_state = self.no_money_state

def set_state(self, state):
self.current_state = state

def insert_money(self, amount):
self.current_state.insert_money(amount)

def eject_money(self):
self.current_state.eject_money()

def select_product(self, name):
self.current_state.select_product(name)

def dispense_product(self, name):
self.current_state.dispense_product(name)

def is_out_of_stock(self):
return all(quantity == 0 for _, quantity in self.items.values())


if __name__ == '__main__':
inventory = {
'coke': [5, 2],
'chips': [3, 1],
'water': [2, 3],
}

machine = VendingMachine(inventory)
machine.insert_money(5)
machine.select_product('chips')