Low level design notes for a simplified Zomato style food ordering app.
The original whiteboard sketches (requirements, UML diagram, flow diagram)
are saved in excalidraw/Zomato Food Delivery App.excalidraw. This file is
the full write up of that design.
1. Overview
A user should be able to find a restaurant near them, look at its menu, build a cart, place an order (now or scheduled for later), choose delivery or pickup, pay for it, and get notified once the order is confirmed.
2. Requirements
2.1 Functional Requirements
- A user can search for restaurants based on location.
- A user can add items to a cart.
- A user can checkout by making a payment.
- A user should be notified once the order is placed successfully.
2.2 Non Functional Requirements
- Each part of the design should be scalable and modifiable. Adding a new payment method, a new order type, or a new notification channel should not require changing existing classes, only adding new ones.
2.3 Actors
- User, the person searching restaurants, building a cart, and placing an order.
- Restaurant, the place that owns a menu of items.
- System (ZomatoApp), the orchestrator that connects the user's actions to the right restaurant, cart, order, payment, and notification logic.
3. Design Approach
The class design follows a bottom up approach, the same way it was worked out on the whiteboard.
- Pull out the nouns from the requirements first, these become the classes: User, Restaurant, MenuItem, Cart, Order, Payment, Notification.
- Pull out the verbs next, these become the methods: search, addToCart, checkout, pay, notify.
- Decide how the classes relate to each other (composition, aggregation, inheritance), based on whether one object can exist without the other.
- Only after the classes and relationships are settled, decide which design pattern fits each moving part (new restaurant list needs one shared instance, new order types keep appearing, new payment methods keep appearing).
UML notation used in the whiteboard
| Notation | Meaning |
|---|---|
<<model>> | A plain data class with getters and setters, no business logic of its own (Restaurant, MenuItem). |
<<interface>> | A contract with no implementation (OrderFactory, PaymentStrategy). |
<<Singleton>> | Exactly one instance of this class exists for the whole app (RestaurantManager, OrderManager). |
| Filled diamond | Composition, the part cannot exist without the whole. |
| Hollow diamond | Aggregation, the part can exist on its own even if the whole is removed. |
1-many | One instance on the diamond side relates to many instances on the other side. |
4. Class Design
models
- User, holds
userId,name,address, and oneCart. The cart is created the moment the user is created, so a user is never without a cart. - Cart, holds a
restaurantreference and a list ofMenuItem. A cart is locked to one restaurant at a time, items can only be added once a restaurant has been picked. This is what stops a user from mixing dishes from two different restaurants in one order. - Restaurant, holds
restaurantId,name,location, and its ownmenu(a list ofMenuItem). ExposesaddMenuItem()andgetMenu(). - MenuItem, a single dish, holds
code,name, andprice. Only ever meaningful as part of one restaurant's menu. - Order (abstract), holds everything common to any order:
orderId,user,restaurant,items,paymentStrategy,total,scheduled. ExposesprocessPayment(), which callspay()on whateverPaymentStrategywas set, and leavesgetType()abstract. - DeliveryOrder (extends
Order), addsuserAddress, returns"Delivery"fromgetType(). - PickupOrder (extends
Order), addsrestaurantAddress, returns"Pickup"fromgetType().
managers
- RestaurantManager (Singleton), keeps the master list of every
restaurant in the system. Exposes
addRestaurant()andsearchByLocation(). - OrderManager (Singleton), keeps the master list of every order
ever placed. Exposes
addOrder()andlistOrders().
Both managers are Singletons because there should only ever be one shared list of restaurants and one shared list of orders across the whole app, not one copy per caller.
factories
- OrderFactory (interface), the contract for building an
Order,createOrder(user, cart, restaurant, items, paymentStrategy, total, orderType). - NowOrderFactory, builds an order for "right now", stamps it with
the current time using
TimeUtils. - ScheduledOrderFactory, builds an order for a chosen future time, stamps it with that time instead.
Both factories do the same two steps internally: first decide
DeliveryOrder vs PickupOrder based on orderType, then fill in the
fields every order needs. Only the timestamp differs between the two.
strategies
- PaymentStrategy (interface), the contract for paying,
pay(amount). - UpiPaymentStrategy, pays using a UPI mobile number.
- CreditCardPaymentStrategy, pays using a card number.
Order only ever calls paymentStrategy.pay(total), it never checks
which payment method it is. Adding NetBanking later only means adding one
more class that implements PaymentStrategy.
services
- NotificationService, a static helper with one method,
notify(order), that prints an order confirmation. Kept separate fromZomatoAppso it can later be swapped for a real SMS or push provider without touching checkout or payment code.
orchestration
- ZomatoApp, does not hold any state of its own. It is the single
entry point that wires the managers, factories, and strategies
together:
searchRestaurants(),selectRestaurant(),addToCart(),checkoutNow()/checkoutScheduled(),checkout(), andpayForOrder().
5. Class Diagram
NetBankingPaymentStrategy was on the original whiteboard next to UPI and Credit
Card, but only UPI and Credit Card exist in the code so far. It is shown here
to keep the diagram matching the whiteboard, and is listed again under
Future Improvements.
6. Class Relationships
| Class A | Class B | Relationship | Reason |
|---|---|---|---|
User | Cart | Composition | A cart is created with its user and has no meaning outside that user. |
Restaurant | MenuItem | Composition | A menu item only exists as part of one restaurant's menu. |
RestaurantManager | Restaurant | Aggregation, 1 to many | The manager keeps a list of restaurants, but a Restaurant still means something on its own. |
OrderManager | Order | Aggregation, 1 to many | Same idea, the manager tracks orders without owning what they mean. |
Order | DeliveryOrder, PickupOrder | Inheritance | Both are a kind of Order and reuse all its shared fields and behavior. |
Order | PaymentStrategy | Association | An order holds a reference to whichever payment method the user picked. |
PaymentStrategy | UpiPaymentStrategy, CreditCardPaymentStrategy, NetBankingPaymentStrategy | Realization | Concrete, swappable payment methods. NetBanking is on the whiteboard but not coded yet. |
OrderFactory | NowOrderFactory, ScheduledOrderFactory | Realization | Concrete, swappable ways to build an order. |
Order | Restaurant, MenuItem | Association | An order remembers which restaurant and which items it came from. |
NotificationService | Order | Association | Reads order details to build the confirmation message. |
ZomatoApp | Managers, Factories, Strategies | Orchestration | ZomatoApp has no state of its own, it only calls the right classes in the right order. |
7. Design Patterns Used
Singleton, in RestaurantManager and OrderManager
Problem: every part of the app needs to read and write the same list of
restaurants, and the same list of orders. If each caller created its own
manager, they would end up with different lists.
Solution: a private constructor plus a static getInstance() guarantees
only one instance is ever created, and everyone shares it.
Factory Method, in OrderFactory, NowOrderFactory, ScheduledOrderFactory
Problem: building an order is more than one step, decide Delivery vs
Pickup, stamp the right time, fill in the shared fields. ZomatoApp
should not need to know these steps.
Solution: createOrder() hides all of that behind one call. ZomatoApp
only decides which factory to use (now or scheduled), not how the order
gets built.
Strategy, in PaymentStrategy, UpiPaymentStrategy, CreditCardPaymentStrategy
Problem: the payment method can change per order, and new methods
(NetBanking, Wallet) will keep getting added.
Solution: Order only ever calls pay() on the interface. Adding a new
payment method never touches Order or ZomatoApp.
Composition and Aggregation, across models and managers
Problem: some objects only make sense as part of another (a MenuItem
without a Restaurant), while others are tracked by a manager but still
stand on their own (a Restaurant without a RestaurantManager).
Solution: model the tight ones as composition (Restaurant to
MenuItem, User to Cart) and the loose ones as aggregation
(RestaurantManager to Restaurant, OrderManager to Order). This
keeps the object lifecycle rules explicit instead of implied.
8. Data Flow
8.1 Original happy flow (from the whiteboard)
User -> Search Restaurant -> Multiple Restaurants -> Menu Card for each restaurant
-> User adds items to cart -> Cart (lists items) -> Order
-> Delivery or Pickup -> Payment -> Notification
8.2 Better flow (from the whiteboard)
Cart -> Checkout -> Delivery or Pickup -> Payment -> Payment Success
-> Order Confirmed -> Notification -> Delivery or Pickup
8.3 What the code actually implements
The implemented flow follows the same spirit as the better flow, with one
change: the Order object is created first (so it already knows if it
is Delivery or Pickup), and only a successful payment leads to a
notification. This keeps a failed payment from ever producing a false
"confirmed" notification.
- Search,
ZomatoApp.searchRestaurants(location)asksRestaurantManagerto filter its list by location. - Select a restaurant, the chosen restaurant is set on the user's cart, locking the cart to that restaurant's menu.
- Add items to cart, items are added one at a time, rejected if no restaurant has been picked yet.
- Checkout,
ZomatoApppicksNowOrderFactoryorScheduledOrderFactory, which builds aDeliveryOrderorPickupOrder. The new order is registered withOrderManager. - Pay, the order calls
pay()on whicheverPaymentStrategywas chosen. - Notify, if payment succeeds,
NotificationServiceprints a confirmation with order id, restaurant, items, total, and schedule. - Clear cart, the user's cart is emptied so a fresh order can start.
8.4 Sequence of calls
9. Assumptions and Scope
- One user can only have one cart at a time, and that cart is tied to one restaurant at a time.
- Payment is treated as always succeeding once a
PaymentStrategyis set (this is a design exercise, no real payment gateway is called). - Restaurant search matches on exact location text, no radius or distance based search.
- No concurrency handling is included, this design assumes a single threaded flow.
10. Possible Future Improvements
These map directly back to the non functional requirement of staying scalable and modifiable:
- Add more
PaymentStrategyimplementations (NetBanking, Wallet) without touchingOrder. - Add more
OrderFactoryimplementations for other checkout types (group order, recurring order) without touchingZomatoApp. - Swap
NotificationService's console print for a real SMS, email, or push notification provider. - Move
RestaurantManagerandOrderManagerfrom in memory lists to a real database, the Singleton boundary makes this a contained change.
11. Folder Structure
Zomato/
README.md This file, requirements, design, and data flow notes
excalidraw/ Original whiteboard sketches (requirements, UML, flow)
src/
Main.java Runs one full happy path order, entry point for the demo
ZomatoApp.java Orchestration class, wires everything together
models/ User, Cart, Restaurant, MenuItem, Order, DeliveryOrder, PickupOrder
managers/ RestaurantManager, OrderManager (both Singleton)
factories/ OrderFactory, NowOrderFactory, ScheduledOrderFactory
strategies/ PaymentStrategy, UpiPaymentStrategy, CreditCardPaymentStrategy
services/ NotificationService
utils/ TimeUtils
12. How to Run
cd src/LLD/Zomato/src
javac -d out $(find . -name "*.java")
java -cp out MainThis runs Main.java, which simulates one full happy path order from
search to notification.