Skip to content

Zomato Food Delivery App (Low Level Design)

Low level design notes for a simplified Zomato style food ordering app.

Builds on Strategy Pattern: Dynamic Runtime Interchangeability, Design Pattern: Simple Factory (Creational) and Singleton Pattern Guide

Stage 4core8 min read

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

  1. A user can search for restaurants based on location.
  2. A user can add items to a cart.
  3. A user can checkout by making a payment.
  4. A user should be notified once the order is placed successfully.

2.2 Non Functional Requirements

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

  1. Pull out the nouns from the requirements first, these become the classes: User, Restaurant, MenuItem, Cart, Order, Payment, Notification.
  2. Pull out the verbs next, these become the methods: search, addToCart, checkout, pay, notify.
  3. Decide how the classes relate to each other (composition, aggregation, inheritance), based on whether one object can exist without the other.
  4. 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

NotationMeaning
<<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 diamondComposition, the part cannot exist without the whole.
Hollow diamondAggregation, the part can exist on its own even if the whole is removed.
1-manyOne instance on the diamond side relates to many instances on the other side.

4. Class Design

models

  • User, holds userId, name, address, and one Cart. The cart is created the moment the user is created, so a user is never without a cart.
  • Cart, holds a restaurant reference and a list of MenuItem. 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 own menu (a list of MenuItem). Exposes addMenuItem() and getMenu().
  • MenuItem, a single dish, holds code, name, and price. 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. Exposes processPayment(), which calls pay() on whatever PaymentStrategy was set, and leaves getType() abstract.
  • DeliveryOrder (extends Order), adds userAddress, returns "Delivery" from getType().
  • PickupOrder (extends Order), adds restaurantAddress, returns "Pickup" from getType().

managers

  • RestaurantManager (Singleton), keeps the master list of every restaurant in the system. Exposes addRestaurant() and searchByLocation().
  • OrderManager (Singleton), keeps the master list of every order ever placed. Exposes addOrder() and listOrders().

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 from ZomatoApp so 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(), and payForOrder().

5. Class Diagram

Loading 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 AClass BRelationshipReason
UserCartCompositionA cart is created with its user and has no meaning outside that user.
RestaurantMenuItemCompositionA menu item only exists as part of one restaurant's menu.
RestaurantManagerRestaurantAggregation, 1 to manyThe manager keeps a list of restaurants, but a Restaurant still means something on its own.
OrderManagerOrderAggregation, 1 to manySame idea, the manager tracks orders without owning what they mean.
OrderDeliveryOrder, PickupOrderInheritanceBoth are a kind of Order and reuse all its shared fields and behavior.
OrderPaymentStrategyAssociationAn order holds a reference to whichever payment method the user picked.
PaymentStrategyUpiPaymentStrategy, CreditCardPaymentStrategy, NetBankingPaymentStrategyRealizationConcrete, swappable payment methods. NetBanking is on the whiteboard but not coded yet.
OrderFactoryNowOrderFactory, ScheduledOrderFactoryRealizationConcrete, swappable ways to build an order.
OrderRestaurant, MenuItemAssociationAn order remembers which restaurant and which items it came from.
NotificationServiceOrderAssociationReads order details to build the confirmation message.
ZomatoAppManagers, Factories, StrategiesOrchestrationZomatoApp 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.

  1. Search, ZomatoApp.searchRestaurants(location) asks RestaurantManager to filter its list by location.
  2. Select a restaurant, the chosen restaurant is set on the user's cart, locking the cart to that restaurant's menu.
  3. Add items to cart, items are added one at a time, rejected if no restaurant has been picked yet.
  4. Checkout, ZomatoApp picks NowOrderFactory or ScheduledOrderFactory, which builds a DeliveryOrder or PickupOrder. The new order is registered with OrderManager.
  5. Pay, the order calls pay() on whichever PaymentStrategy was chosen.
  6. Notify, if payment succeeds, NotificationService prints a confirmation with order id, restaurant, items, total, and schedule.
  7. Clear cart, the user's cart is emptied so a fresh order can start.

8.4 Sequence of calls

Loading diagram…

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 PaymentStrategy is 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 PaymentStrategy implementations (NetBanking, Wallet) without touching Order.
  • Add more OrderFactory implementations for other checkout types (group order, recurring order) without touching ZomatoApp.
  • Swap NotificationService's console print for a real SMS, email, or push notification provider.
  • Move RestaurantManager and OrderManager from 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 Main

This runs Main.java, which simulates one full happy path order from search to notification.

In this topic

Whiteboard diagram

Open full screen
Loading diagram…
Zomato Food Delivery App

Done with this topic?

How well do you know it?

PreviousLow-Level Design (LLD): Parking Lot System