Design Restaurant Management System

Last Updated: December 19, 2025

Ashish

Ashish Pratap Singh

hard

In this chapter, we will explore the low-level design of a restaurant management system in detail.

Lets start by clarifying the requirements:

1. Clarifying 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:

After gathering the details, we can summarize the key system requirements.

1.1 Functional Requirements

  • Staff Management: The system should support different staff roles, specifically Waiters and Chefs.
  • Table Management: The system must track the status of each table (e.g., AVAILABLE, OCCUPIED).
  • Menu Management: The system must maintain a menu of items, each with a name and a price.
  • Order Management: Waiters can create an order for a specific table. The system must track the status of each individual order item.
  • Kitchen Workflow: Chefs receive and prepare orders. The system must notify the correct waiter when an order item is ready for pickup.
  • Billing: The system must be able to generate a bill for a given order. The bill calculation should be flexible enough to dynamically add charges like taxes and service fees.

1.2 Non-Functional Requirements

  • Modularity: The design should follow solid object-oriented principles with clear separation of concerns.
  • Extensibility: The design should be easy to extend to allow future enhancements. For example, it should be easy to add new types of bill charges (e.g., discounts) or new order item states without modifying existing code.
  • Maintainability: The code should be clean, readable, and easy to maintain, leveraging appropriate design patterns to solve common problems.
  • Concurrency: The system should be designed to handle multiple orders and staff interactions concurrently without data corruption.
  • Clarity: The system should provide clear console output to demonstrate the workflow, such as state changes and notifications.

2. Identifying Core Entities

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:

1. Manage Staff, Tables, and the Menu.

This points to several foundational entities:

  1. Staff: An abstract base class for employees. Concrete implementations are Waiter and Chef.
  2. Table: Represents a physical table in the restaurant, with a status managed by the TableStatus enum.
  3. Menu: A container for all available food and drink items.
  4. MenuItem: Represents a single dish or drink with a name and price.
  5. Restaurant: A Singleton class that acts as a central registry, holding all staff, tables, and the menu.

2. Manage the entire order lifecycle, from creation to serving.

This is the core workflow and involves several interacting entities:

  1. Order: A container object that groups multiple OrderItems for a specific Table.
  2. OrderItem: A crucial entity representing a single menu item within an order

3. Generate a flexible bill with dynamic charges like taxes and service fees.

This calls for a Bill entity. The requirement for flexibility is met using the Decorator pattern.

4. Provide a simplified, high-level interface to the system.

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.

3. Designing Classes and Relationships

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.

3.1 Class Definitions

The system is composed of several types of classes, each with a distinct role.

Enums

TableStatus

Defines the status of a table (AVAILABLE, OCCUPIED, RESERVED).

Enums

Data Classes

Table

Represents a dining table with an ID, capacity, and status.

Table

A simple data class for a menu item, holding its ID, name, and price.

MenuItem

A container class that holds a collection of MenuItems for the restaurant.

Menu

Order

Represents a customer's order for a specific table. It contains a list of OrderItems.

Order

Bill

A wrapper class that holds a BillComponent and provides a method to print the final, decorated bill.

Core Classes

Staff (Abstract Class)

A base class for all employees, holding common properties like ID and name.

Staff
  • 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.

OrderItem

OrderItem

A 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).

BaseBill

The concrete component that calculates the initial total based on the order's items.

Restaurant (Singleton)

Restaurant

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.

RestaurantManagementSystemFacade

It hides the system's internal complexity and provides a simple, unified API for all major operations like taking orders and generating bills.

3.2 Class Relationships

The relationships between classes define the system's structure and data flow.

Composition

  • The Restaurant is composed of Tables, Staff (Chefs and Waiters), and a Menu.
  • An Order is composed of a list of OrderItems.
  • A Menu is composed of MenuItems.

Association

  • An OrderItem is associated with a single MenuItem.
  • A Bill is associated with a BillComponent (which can be a BaseBill or a decorated one).
  • An OrderItem (Subject) is associated with a list of OrderObservers (the Waiter).
  • An OrderItem (Context) is associated with a single, current OrderItemState.

Inheritance

  • Chef and Waiter inherit from the abstract Staff class.
  • Waiter implements the OrderObserver interface.
  • Concrete state classes (OrderedState, etc.) implement the OrderItemState interface.
  • Concrete command classes (PrepareOrderCommand, etc.) implement the Command interface.
  • BillDecorator implements BillComponent, and concrete decorators like TaxDecorator extend BillDecorator.

Dependency

  • The RestaurantManagementSystemFacade depends on Command objects to process orders.
  • The ReadyForPickupState depends on the OrderItem's notifyObservers method to signal waiters.
  • A client depends on the RestaurantManagementSystemFacade to interact with the system.

3.3 Key Design Patterns

State Pattern

OrderItemState

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.

Observer Pattern

OrderObserver

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.

Command Pattern

Command

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).

Decorator Pattern

Decorator

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.

Facade Pattern

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.

Singleton Pattern

Restaurant and RestaurantManagementSystemFacade are implemented as singletons to ensure a single, globally accessible point of control for restaurant assets and system operations.

3.4 Full Class Diagram

Restaurant Management System Class Diagram

4. Implementation

4.1 TableStatus Enum

Defines 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"

4.2 Table Class

Represents 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 = status

Each table has a unique ID, a seating capacity, and a status to indicate availability.

4.3 Staff

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).

4.4 Menu and MenuItem

Menu 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._price

Restaurant

1class 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._menu

OrderItem

This 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._order
  • Context for State: The OrderItem delegates state transition logic to its state object by calling state.next(this).
  • Subject for Observer: It also maintains a list of observers (typically the waiter who took the order). When its state transitions out of ReadyForPickup, it notifies these observers. This is a powerful combination of the State and Observer patterns.

Order

An 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._items

OrderObserver

1class OrderObserver(ABC):
2    @abstractmethod
3    def update(self, item: 'OrderItem'):
4        pass

Command

This 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)
  • Encapsulated Requests: A PrepareOrderCommand encapsulates everything needed to prepare an order: the Order itself and the Chef who will prepare it.

OrderItemState

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.

Bill and Decorators

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"

Bill Decorators

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"

RestaurantManagementSystemFacade

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)
  • Facade Pattern: This class provides simple methods like takeOrder and generateBill, hiding the complex interactions between states, observers, commands, and decorators.
  • Orchestration: The facade's methods are responsible for orchestrating the creation and wiring of different objects. For example, takeOrder is responsible for finding the staff, creating the Order and OrderItem objects, and crucially, registering the Waiter as an observer on each OrderItem.

RestaurantManagementSystemDemo

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()

5. Run and Test

Files26
commands
decorators
entities
enum
observer
states
restaurant_management_system_demo.py
main
restaurant_management_system_facade.py
restaurant_management_system_demo.pymain
Output

6. Quiz

Design Restaurant Management System Quiz

1 / 20
Multiple Choice

Which entity is primarily responsible for grouping multiple ordered menu items for a single dining table?

How helpful was this article?

Comments


0/2000

No comments yet. Be the first to comment!

Copilot extension content script