Last Updated: December 19, 2025
A Restaurant Management System is software that helps manage the day-to-day operations of a restaurant, including table reservations, order taking, kitchen coordination, billing, menu updates, and staff management.
It streamlines workflows, improves customer service, and increases overall efficiency.
In this chapter, we will explore the low-level design of a restaurant management system in detail.
Lets start by clarifying the requirements:
Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions and better define the scope of the system.
Here is an example of how a conversation between the candidate and the interviewer might unfold:
Candidate: What are the primary actors in this system? Are we designing a customer-facing app for ordering, or an internal system for staff management
Interviewer: Let's focus on the internal system for staff (Waiters, Chefs) to manage orders and billing. Customer interaction is out of scope.
Candidate: Should the system handle table reservations in advance, or only manage walk-in customers and their current table status?
Interviewer: Good question. For now, let’s focus on the core workflow: taking an order, preparing it, serving it, and generating a bill. We can assume tables are managed in real-time (available, occupied), but a full reservation feature is not required.
Candidate: Should the system track inventory for menu items? For example, knowing if we're out of a certain ingredient.
Interviewer: That's a great extension, but let’s keep it out of scope for this interview. Assume all menu items are always available.
Candidate: How are orders assigned to chefs?
Interviewer: Let's assume a simple mechanism for now, like assigning an order to the first available chef.
Candidate: How does a waiter know when an order item is ready for pickup from the kitchen?
Interviewer: The waiter who takes the order should be notified specifically when an item they are responsible for is ready for pickup.
Candidate: How should payments be handled? Do we need to integrate with a payment gateway for credit cards, or just calculate the final bill?
Interviewer: Just focus on calculating the final bill. The bill should be flexible enough to include things like taxes or service charges. Assume payment processing is handled by a separate system.
After gathering the details, we can summarize the key system requirements.
Core entities are the fundamental building blocks of our system. We identify them by analyzing key nouns (e.g., order, table, menu item, staff, kitchen view, payment) and actions (e.g., create, customize, assign, track, pay) from the functional requirements. These typically translate directly into classes, enums, or interfaces in an object-oriented design.
Let’s walk through the functional requirements and extract the relevant entities:
This points to several foundational entities:
Staff: An abstract base class for employees. Concrete implementations are Waiter and Chef.Table: Represents a physical table in the restaurant, with a status managed by the TableStatus enum.Menu: A container for all available food and drink items.MenuItem: Represents a single dish or drink with a name and price.Restaurant: A Singleton class that acts as a central registry, holding all staff, tables, and the menu.This is the core workflow and involves several interacting entities:
This calls for a Bill entity. The requirement for flexibility is met using the Decorator pattern.
To hide the internal complexity we introduce a facade called RestaurantManagementSystemFacade. It acts as a Singleton and provides a simple API for all major operations like taking an order or generating a bill.
These core entities define the essential abstractions of a Restaurant Management System and will guide the structure of your low-level design and class diagrams.
This section breaks down the system's architecture into its fundamental classes, their responsibilities, and the relationships that connect them. We also explore the key design patterns that provide robustness and flexibility to the solution.
The system is composed of several types of classes, each with a distinct role.
TableStatus
Defines the status of a table (AVAILABLE, OCCUPIED, RESERVED).
TableRepresents a dining table with an ID, capacity, and status.
MenuItemA simple data class for a menu item, holding its ID, name, and price.
MenuA container class that holds a collection of MenuItems for the restaurant.
OrderRepresents a customer's order for a specific table. It contains a list of OrderItems.
BillA wrapper class that holds a BillComponent and provides a method to print the final, decorated bill.
Staff (Abstract Class)A base class for all employees, holding common properties like ID and name.
Chef & Waiter: Concrete implementations of Staff. The Chef is responsible for preparing orders. The Waiter takes orders, serves them, and, crucially, acts as an Observer to get notified when items are ready for pickup.OrderItemA central class representing a single item within an order.
It acts as the Context for the State pattern (delegating state transitions to its state object) and the Subject for the Observer pattern (notifying its observers, i.e., the Waiter).
BaseBillThe concrete component that calculates the initial total based on the order's items.
Restaurant (Singleton)A class that holds all the restaurant's core assets like staff, tables, and the menu.
RestaurantManagementSystemFacade (Singleton & Facade)The primary entry point for the application.
It hides the system's internal complexity and provides a simple, unified API for all major operations like taking orders and generating bills.
The relationships between classes define the system's structure and data flow.
Restaurant is composed of Tables, Staff (Chefs and Waiters), and a Menu.Order is composed of a list of OrderItems.Menu is composed of MenuItems.OrderItem is associated with a single MenuItem.Bill is associated with a BillComponent (which can be a BaseBill or a decorated one).OrderItem (Subject) is associated with a list of OrderObservers (the Waiter).OrderItem (Context) is associated with a single, current OrderItemState.Chef and Waiter inherit from the abstract Staff class.Waiter implements the OrderObserver interface.OrderedState, etc.) implement the OrderItemState interface.PrepareOrderCommand, etc.) implement the Command interface.BillDecorator implements BillComponent, and concrete decorators like TaxDecorator extend BillDecorator.RestaurantManagementSystemFacade depends on Command objects to process orders.ReadyForPickupState depends on the OrderItem's notifyObservers method to signal waiters.RestaurantManagementSystemFacade to interact with the system.The lifecycle of an OrderItem is managed using the State pattern. The OrderItem (Context) delegates its behavior to different OrderItemState objects. This cleanly separates the logic for each stage of preparation and makes the flow robust.
This pattern is crucial for communication between the kitchen and serving staff. The OrderItem (Subject) notifies the Waiter (Observer) when it is ready for pickup. This decouples the chef's actions from the waiter's responsibilities.
The Command pattern encapsulates a request as an object (e.g., PrepareOrderCommand). This decouples the object that issues a request (the facade) from the object that performs it (the Chef or Waiter).
The BillComponent and its decorators (TaxDecorator, ServiceChargeDecorator) are used to add responsibilities (costs) to the bill dynamically. This allows for flexible calculation of the final total without altering the base bill object.
The RestaurantManagementSystemFacade class serves as a facade. It provides a simple, high-level API (takeOrder, generateBill) that hides the complex internal workflows involving states, observers, commands, and decorators.
Restaurant and RestaurantManagementSystemFacade are implemented as singletons to ensure a single, globally accessible point of control for restaurant assets and system operations.
TableStatus EnumDefines the current state of a table — whether it's free, in use, or booked.
1class TableStatus(Enum):
2 AVAILABLE = "AVAILABLE"
3 OCCUPIED = "OCCUPIED"
4 RESERVED = "RESERVED"Table ClassRepresents a dining table in the restaurant.
1class Table:
2 def __init__(self, table_id: int, capacity: int):
3 self._id = table_id
4 self._capacity = capacity
5 self._status = TableStatus.AVAILABLE
6
7 @property
8 def id(self) -> int:
9 return self._id
10
11 @property
12 def capacity(self) -> int:
13 return self._capacity
14
15 @property
16 def status(self) -> TableStatus:
17 return self._status
18
19 @status.setter
20 def status(self, status: TableStatus):
21 self._status = statusEach table has a unique ID, a seating capacity, and a status to indicate availability.
Staff is the base class shared by Chef and Waiter.
1class Staff(ABC):
2 def __init__(self, staff_id: str, name: str):
3 self._id = staff_id
4 self._name = name
5
6 @property
7 def id(self) -> str:
8 return self._id
9
10 @property
11 def name(self) -> str:
12 return self._name
13
14class Chef(Staff):
15 def __init__(self, staff_id: str, name: str):
16 super().__init__(staff_id, name)
17
18 def prepare_order(self, order: 'Order'):
19 print(f"Chef {self._name} received order {order.order_id} and is starting preparation.")
20 for item in order.order_items:
21 # Chef's action triggers the first state change for each item
22 item.change_state(PreparingState())
23
24class Waiter(Staff, OrderObserver):
25 def __init__(self, staff_id: str, name: str):
26 super().__init__(staff_id, name)
27
28 def serve_order(self, order: 'Order'):
29 print(f"Waiter {self._name} is serving order {order.order_id}")
30 for item in order.order_items:
31 item.change_state(ServedState())
32
33 def update(self, item: 'OrderItem'):
34 print(f">>> WAITER {self._name} NOTIFIED: Item '{item.menu_item.name}' "
35 f"for table {item.order.table_id} is READY FOR PICKUP.")Chef prepares an order and transitions each item to the PREPARING state.Waiter observes order items and serves them once notified (implements Observer pattern via OrderObserver).Menu and MenuItemMenu is a container of MenuItems, each identified by a unique ID. Allows adding and retrieving items efficiently.
1class MenuItem:
2 def __init__(self, item_id: str, name: str, price: float):
3 self._id = item_id
4 self._name = name
5 self._price = price
6
7 @property
8 def id(self) -> str:
9 return self._id
10
11 @property
12 def name(self) -> str:
13 return self._name
14
15 @property
16 def price(self) -> float:
17 return self._price1class Restaurant:
2 _instance = None
3 _lock = threading.Lock()
4
5 def __new__(cls):
6 if cls._instance is None:
7 with cls._lock:
8 if cls._instance is None:
9 cls._instance = super().__new__(cls)
10 cls._instance._initialized = False
11 return cls._instance
12
13 def __init__(self):
14 if not self._initialized:
15 self._waiters: Dict[str, Waiter] = {}
16 self._chefs: Dict[str, Chef] = {}
17 self._tables: Dict[int, Table] = {}
18 self._menu = Menu()
19 self._initialized = True
20
21 @classmethod
22 def get_instance(cls):
23 return cls()
24
25 def add_waiter(self, waiter: Waiter):
26 self._waiters[waiter.id] = waiter
27
28 def get_waiter(self, waiter_id: str) -> Optional[Waiter]:
29 return self._waiters.get(waiter_id)
30
31 def add_chef(self, chef: Chef):
32 self._chefs[chef.id] = chef
33
34 def get_chef(self, chef_id: str) -> Optional[Chef]:
35 return self._chefs.get(chef_id)
36
37 def get_chefs(self) -> List[Chef]:
38 return list(self._chefs.values())
39
40 def get_waiters(self) -> List[Waiter]:
41 return list(self._waiters.values())
42
43 def add_table(self, table: Table):
44 self._tables[table.id] = table
45
46 @property
47 def menu(self) -> Menu:
48 return self._menuThis class represents a single item within an order and acts as the Context for the OrderItemState and the Subject for the OrderObserver.
1class OrderItem:
2 def __init__(self, menu_item: MenuItem, order: 'Order'):
3 self._menu_item = menu_item
4 self._order = order
5 self._state = OrderedState()
6 self._observers: List[OrderObserver] = []
7
8 def change_state(self, new_state: 'OrderItemState'):
9 self._state = new_state
10 print(f"Item '{self._menu_item.name}' state changed to: {new_state.get_status()}")
11
12 def next_state(self):
13 self._state.next(self)
14
15 def set_state(self, state: OrderItemState):
16 self._state = state
17
18 def add_observer(self, observer: OrderObserver):
19 self._observers.append(observer)
20
21 def notify_observers(self):
22 for observer in self._observers[:]: # Create a copy to avoid modification during iteration
23 observer.update(self)
24
25 @property
26 def menu_item(self) -> MenuItem:
27 return self._menu_item
28
29 @property
30 def order(self) -> 'Order':
31 return self._orderAn order is associated with a table and contains multiple OrderItems.
1class Order:
2 def __init__(self, order_id: int, table_id: int):
3 self._order_id = order_id
4 self._table_id = table_id
5 self._items: List[OrderItem] = []
6
7 def add_item(self, item: OrderItem):
8 self._items.append(item)
9
10 def get_total_price(self) -> float:
11 return sum(item.menu_item.price for item in self._items)
12
13 @property
14 def order_id(self) -> int:
15 return self._order_id
16
17 @property
18 def table_id(self) -> int:
19 return self._table_id
20
21 @property
22 def order_items(self) -> List[OrderItem]:
23 return self._items1class OrderObserver(ABC):
2 @abstractmethod
3 def update(self, item: 'OrderItem'):
4 passThis pattern encapsulates a request as an object, allowing us to decouple the object that issues a request from the object that performs it.
1class Command(ABC):
2 @abstractmethod
3 def execute(self):
4 pass
5
6class PrepareOrderCommand(Command):
7 def __init__(self, order: Order, chef: Chef):
8 self._order = order
9 self._chef = chef
10
11 def execute(self):
12 self._chef.prepare_order(self._order)
13
14class ServeOrderCommand(Command):
15 def __init__(self, order: Order, waiter: Waiter):
16 self._order = order
17 self._waiter = waiter
18
19 def execute(self):
20 self._waiter.serve_order(self._order)An OrderItem progresses through several states: Ordered -> Preparing -> ReadyForPickup -> Served. The State pattern is used to manage this flow and the behavior in each state.
1class OrderItemState(ABC):
2 @abstractmethod
3 def next(self, item: 'OrderItem'):
4 pass
5
6 @abstractmethod
7 def prev(self, item: 'OrderItem'):
8 pass
9
10 @abstractmethod
11 def get_status(self) -> str:
12 pass
13
14
15class OrderedState(OrderItemState):
16 def next(self, item: 'OrderItem'):
17 item.set_state(PreparingState())
18
19 def prev(self, item: 'OrderItem'):
20 print("This is the initial state.")
21
22 def get_status(self) -> str:
23 return "ORDERED"
24
25
26class PreparingState(OrderItemState):
27 def next(self, item: 'OrderItem'):
28 item.set_state(ReadyForPickupState())
29
30 def prev(self, item: 'OrderItem'):
31 item.set_state(OrderedState())
32
33 def get_status(self) -> str:
34 return "PREPARING"
35
36
37class ReadyForPickupState(OrderItemState):
38 def next(self, item: 'OrderItem'):
39 # This is the key state. When it transitions, it notifies observers.
40 item.notify_observers()
41
42 def prev(self, item: 'OrderItem'):
43 item.set_state(PreparingState())
44
45 def get_status(self) -> str:
46 return "READY_FOR_PICKUP"
47
48
49class ServedState(OrderItemState):
50 def next(self, item: 'OrderItem'):
51 print("This is the final state.")
52
53 def prev(self, item: 'OrderItem'):
54 print("Cannot revert a served item.")
55
56 def get_status(self) -> str:
57 return "SERVED"Each state class encapsulates the logic for that state. The next() method defines the valid transition to the subsequent state.
The ReadyForPickupState is special. Its next() method is responsible for triggering the notifyObservers() call on the OrderItem. This is how the system signals that a chef has finished preparing an item.
1# Decorator Pattern for Bills
2class BillComponent(ABC):
3 @abstractmethod
4 def calculate_total(self) -> float:
5 pass
6
7 @abstractmethod
8 def get_description(self) -> str:
9 pass
10
11class Bill:
12 def __init__(self, component: BillComponent):
13 self._component = component
14
15 def print_bill(self):
16 print("\n--- BILL ---")
17 print(f"Description: {self._component.get_description()}")
18 print(f"Total: ${self._component.calculate_total():.2f}")
19 print("------------")
20
21class BaseBill(BillComponent):
22 def __init__(self, order: Order):
23 self._order = order
24
25 def calculate_total(self) -> float:
26 return self._order.get_total_price()
27
28 def get_description(self) -> str:
29 return "Order Items"The Decorator Pattern adds dynamic pricing behaviors (e.g., tax, service charge) without modifying the core billing logic.
This pattern allows behavior (in this case, costs) to be added to an object dynamically. It's perfect for adding taxes, service charges, or discounts to a bill.
1class BillDecorator(BillComponent):
2 def __init__(self, component: BillComponent):
3 self._wrapped = component
4
5 def calculate_total(self) -> float:
6 return self._wrapped.calculate_total()
7
8 def get_description(self) -> str:
9 return self._wrapped.get_description()
10
11
12class TaxDecorator(BillDecorator):
13 def __init__(self, component: BillComponent, tax_rate: float):
14 super().__init__(component)
15 self._tax_rate = tax_rate
16
17 def calculate_total(self) -> float:
18 return super().calculate_total() * (1 + self._tax_rate)
19
20 def get_description(self) -> str:
21 return super().get_description() + f", Tax @{self._tax_rate * 100}%"
22
23
24class ServiceChargeDecorator(BillDecorator):
25 def __init__(self, component: BillComponent, charge: float):
26 super().__init__(component)
27 self._service_charge = charge
28
29 def calculate_total(self) -> float:
30 return super().calculate_total() + self._service_charge
31
32 def get_description(self) -> str:
33 return super().get_description() + ", Service Charge"This class is a Singleton that provides a simplified, high-level interface to the complex subsystem.
1class RestaurantManagementSystemFacade:
2 _instance = None
3 _lock = threading.Lock()
4
5 def __new__(cls):
6 if cls._instance is None:
7 with cls._lock:
8 if cls._instance is None:
9 cls._instance = super().__new__(cls)
10 cls._instance._initialized = False
11 return cls._instance
12
13 def __init__(self):
14 if not self._initialized:
15 self._restaurant = Restaurant.get_instance()
16 self._order_id_counter = 1
17 self._orders: Dict[int, Order] = {}
18 self._initialized = True
19
20 @classmethod
21 def get_instance(cls):
22 return cls()
23
24 def add_table(self, table_id: int, capacity: int) -> Table:
25 table = Table(table_id, capacity)
26 self._restaurant.add_table(table)
27 return table
28
29 def add_waiter(self, waiter_id: str, name: str) -> Waiter:
30 waiter = Waiter(waiter_id, name)
31 self._restaurant.add_waiter(waiter)
32 return waiter
33
34 def add_chef(self, chef_id: str, name: str) -> Chef:
35 chef = Chef(chef_id, name)
36 self._restaurant.add_chef(chef)
37 return chef
38
39 def add_menu_item(self, item_id: str, name: str, price: float) -> MenuItem:
40 item = MenuItem(item_id, name, price)
41 self._restaurant.menu.add_item(item)
42 return item
43
44 def take_order(self, table_id: int, waiter_id: str, menu_item_ids: List[str]) -> Order:
45 waiter = self._restaurant.get_waiter(waiter_id)
46 if waiter is None:
47 raise ValueError("Invalid waiter ID.")
48
49 # For simplicity, we get the first available chef
50 chefs = self._restaurant.get_chefs()
51 if not chefs:
52 raise RuntimeError("No chefs available.")
53 chef = chefs[0]
54
55 order = Order(self._order_id_counter, table_id)
56 self._order_id_counter += 1
57
58 for item_id in menu_item_ids:
59 menu_item = self._restaurant.menu.get_item(item_id)
60 order_item = OrderItem(menu_item, order)
61 # Waiter subscribes to each item to get notified when it's ready
62 order_item.add_observer(waiter)
63 order.add_item(order_item)
64
65 # The Command pattern decouples the waiter (invoker) from the chef (receiver)
66 prepare_order_command = PrepareOrderCommand(order, chef)
67 prepare_order_command.execute()
68
69 self._orders[order.order_id] = order
70 return order
71
72 def mark_items_as_ready(self, order_id: int):
73 order = self._orders[order_id]
74 print(f"\nChef has finished preparing order {order.order_id}")
75
76 for item in order.order_items:
77 # Preparing -> ReadyForPickup -> Notifies Observer (Waiter)
78 item.next_state()
79 item.next_state()
80
81 def serve_order(self, waiter_id: str, order_id: int):
82 order = self._orders[order_id]
83 waiter = self._restaurant.get_waiter(waiter_id)
84
85 serve_order_command = ServeOrderCommand(order, waiter)
86 serve_order_command.execute()
87
88 def generate_bill(self, order_id: int) -> Bill:
89 order = self._orders[order_id]
90 # The Decorator pattern adds charges dynamically
91 bill_component = BaseBill(order)
92 bill_component = TaxDecorator(bill_component, 0.08) # 8% tax
93 bill_component = ServiceChargeDecorator(bill_component, 5.00) # $5 flat service charge
94
95 return Bill(bill_component)The demo class validates the entire system by simulating a typical restaurant scenario.
1class RestaurantManagementSystemDemo:
2 @staticmethod
3 def main():
4 # --- 1. System Setup using the Restaurant Singleton ---
5 print("=== Initializing Restaurant System ===")
6 rms_facade = RestaurantManagementSystemFacade.get_instance()
7
8 # --- 2. Add table and staff ---
9 table1 = rms_facade.add_table(1, 4)
10 chef1 = rms_facade.add_chef("CHEF01", "Gordon")
11 waiter1 = rms_facade.add_waiter("W01", "Alice")
12
13 # --- 3. Add menu items ---
14 pizza = rms_facade.add_menu_item("PIZZA01", "Margherita Pizza", 12.50)
15 pasta = rms_facade.add_menu_item("PASTA01", "Carbonara Pasta", 15.00)
16 coke = rms_facade.add_menu_item("DRINK01", "Coke", 2.50)
17 print("Initialization Complete.\n")
18
19 # --- 4. Scenario: A waiter takes an order for a table ---
20 # The Command Pattern is used inside the rms_facade.take_order() method.
21 print("=== SCENARIO 1: Taking an order ===")
22 order1 = rms_facade.take_order(table1.id, waiter1.id, [pizza.id, coke.id])
23 print(f"Order taken successfully. Order ID: {order1.order_id}")
24
25 # --- 5. Scenario: Chef prepares food and notifies waiter ---
26 print("\n=== SCENARIO 2: Chef prepares, Waiter gets notified ===")
27 rms_facade.mark_items_as_ready(order1.order_id)
28 rms_facade.serve_order(waiter1.id, order1.order_id)
29
30 # --- 6. Scenario: Generate a bill with taxes and service charges ---
31 # The Decorator Pattern is used inside rms_facade.generate_bill().
32 print("\n=== SCENARIO 3: Generating the bill ===")
33 final_bill = rms_facade.generate_bill(order1.order_id)
34 final_bill.print_bill()
35
36if __name__ == "__main__":
37 RestaurantManagementSystemDemo.main()Which entity is primarily responsible for grouping multiple ordered menu items for a single dining table?
No comments yet. Be the first to comment!