Compile Ready
All low level design problems
Low Level Design/Advanced/Core Systems

Design a Food Delivery System

Restaurants, menus, carts, orders, and delivery-partner matching — Swiggy/DoorDash object modelling.

Advanced 60m interview 19m read High frequency Popularity 87
State Strategy Observer Factory Method Swiggy Amazon Uber Flipkart

Problem Statement

Design the object model for a food delivery system like Swiggy or Zomato. Customers browse restaurants, add available menu items to a cart, place an order, and then track the order as the restaurant accepts it, prepares it, hands it to a delivery partner, and the partner delivers it.

The interview focus is the order lifecycle and partner matching. A strong solution should make state transitions explicit, keep assignment policy pluggable, calculate pricing in one place, and notify customers, restaurants, and partners whenever the order status changes.

Business context

Food delivery is a high-frequency intermediate LLD problem because it combines marketplace modelling with a lifecycle that is easy to get wrong. Restaurants change menus, customers edit carts, partners move across the city, and the same order must not jump from Placed directly to Delivered.

Interviewers use it to test whether you can separate domain state from service orchestration: Order owns valid transitions, AssignmentStrategy owns partner choice, OrderService coordinates the use case, and OrderObserver fans out status notifications without coupling the lifecycle to SMS, push, or email.

Functional Requirements

  • Register restaurants with a location, availability flag, and menu items.

  • Allow customers to build a cart for exactly one restaurant at a time.

  • Validate item availability and quantity before adding items to the cart.

  • Place an order from a non-empty cart and compute subtotal, delivery fee, platform fee, tax, and total.

  • Move the order through Placed -> Accepted -> Preparing -> OutForDelivery -> Delivered, with Cancelled as a terminal failure state.

  • Let the restaurant accept an order and mark it as preparing before dispatch.

  • Assign a delivery partner using a pluggable nearest or least-busy strategy.

  • Notify customers, restaurants, and delivery partners whenever the order status changes.

  • Reject invalid lifecycle transitions such as dispatch before preparation or delivery before dispatch.

Non-Functional Requirements

Lifecycle correctness

The state machine must be the source of truth for valid transitions. Service methods should ask the order to transition rather than mutate a status enum directly.

Low assignment latency

For an interview-scale in-memory design, scanning partners is acceptable. The strategy seam lets production replace it with a geospatial index later.

Extensibility

New pricing plans, partner assignment policies, notification channels, and cancellation rules should be additive classes with minimal changes to the core flow.

Auditability

Every order should keep a status timeline so support teams can explain when the restaurant accepted, when dispatch happened, and why a cancellation was allowed.

Consistency under concurrency

Order transition and partner assignment should be performed atomically per order so two dispatch attempts do not assign two partners.

Requirement Clarification

QCan a cart contain items from multiple restaurants?

No for the base design. One cart maps to one restaurant, which keeps pricing, preparation, and partner pickup simple. Multi-restaurant checkout is an extension.

QWho can cancel an order and until when?

Assume cancellation is allowed before delivery and becomes a terminal Cancelled state. Real systems can add policy checks based on payment, preparation stage, or partner arrival.

QDo we model payments and refunds?

Only pricing is in scope. Payment authorization, refunds, coupon validation, and wallet balances are boundary integrations that can be added around OrderService.

QHow exact should location matching be?

Use a simple distance function in the LLD. The important design point is that nearest and least-busy are interchangeable AssignmentStrategy implementations.

QShould notifications be synchronous?

In the interview implementation observers run synchronously to show the pattern. In production they would usually publish events to a queue for retry and fanout.

UML Class Diagram

Rendering diagram…
The service coordinates the use case, but the volatile decisions are behind seams: order status is a State machine, partner matching is a Strategy, pricing feeds the Factory Method, and notifications are Observers.

Sequence Diagram

Rendering diagram…
The lifecycle is visible as a sequence of state transitions. Partner assignment happens only after preparation and before the out-for-delivery notification.

Entity Identification

Restaurant

Owns restaurant identity, location, menu, and whether the kitchen is currently accepting orders.

idnamelocationmenuByIdacceptingOrders

MenuItem

Represents one sellable item with price and availability. The cart reads it, but availability remains controlled by the restaurant menu.

idnamepricePaiseavailable

Cart

Temporary customer basket for one restaurant. It validates quantity and item availability, and exposes an immutable snapshot for order creation.

customerIdrestaurantlines

Order

Aggregate root for the order lifecycle. It owns items, pricing quote, partner, status timeline, and all valid transitions.

idcustomerIdrestaurantitemsquotestatetimeline

DeliveryPartner

Courier domain object with location, online status, capacity, and active order count used by assignment strategies.

idnamecurrentLocationonlineactiveOrderIds

AssignmentStrategy

Policy interface for partner matching. Nearest and least-busy are alternative implementations with the same service contract.

assign(order, partners)

OrderService

Application facade. Registers restaurants and partners, places orders through the factory, invokes transitions, assigns partners, and notifies observers.

restaurantsorderspartnersobserversassignmentStrategyorderFactory

OrderObserver

Notification seam. Customer, restaurant, and partner channels react to order updates without being embedded inside the state machine.

onOrderUpdated(order, event)

Design Patterns Used

State

Order delegates transition behavior to status-specific state objects. Invalid jumps such as Placed to OutForDelivery fail at the domain boundary instead of relying on scattered conditionals.

Strategy

AssignmentStrategy lets the service choose nearest or least-busy partner matching at runtime. The same pattern also keeps pricing rules behind PricingStrategy.

Observer

OrderObserver decouples lifecycle changes from notification delivery. Adding WhatsApp, push, email, or restaurant dashboard alerts does not change Order.

Factory Method

OrderFactory defines the order creation skeleton and delegates the actual object construction to newOrder, so premium, scheduled, or group-order factories can vary creation without changing OrderService.

Step-by-Step Design

  1. 1Start with the core marketplace nouns

    Keep the base model small: Restaurant owns a menu and location, MenuItem owns price and availability, and Cart is tied to one restaurant so pickup and billing stay coherent.

    public final class Cart {
        private final Restaurant restaurant;
        private final Map<String, Line> lines = new LinkedHashMap<>();
        public void addItem(String itemId, int quantity) {
            MenuItem item = restaurant.menuItem(itemId);
            if (!item.isAvailable()) throw new IllegalStateException();
            lines.put(itemId, new Line(item, quantity));
        }
    }
  2. 2Make order status a real state machine

    Order should expose intent methods such as accept, markPreparing, and markDelivered. Each method delegates to the current state so invalid transitions are impossible to ignore.

    public synchronized void markOutForDelivery() {
        state.dispatch(this);
    }
    
    private static final class PreparingState extends BaseState {
        public OrderStatus status() { return OrderStatus.PREPARING; }
        public void dispatch(Order order) { order.transitionTo(new OutForDeliveryState()); }
    }
  3. 3Snapshot price at order creation

    The order should carry a PricingQuote computed from the cart. Later menu price changes should not rewrite what the customer agreed to pay.

  4. 4Hide partner matching behind a strategy

    The service should not know whether matching means closest by location, least busy, highest rating, or partner preference. It asks AssignmentStrategy for one available partner.

    public interface AssignmentStrategy {
        DeliveryPartner assign(Order order, List<DeliveryPartner> partners);
    }
  5. 5Notify through observers, not conditionals

    After each successful transition, OrderService emits an update to registered observers. Channels can be added or removed without touching lifecycle code.

  6. 6Keep the service as orchestration only

    OrderService validates registered restaurants, invokes the factory, calls transition methods, asks the assignment strategy, and notifies observers. It should not contain status-specific branching.

Complete Java Implementation

Loading…

Explanation of Every Class

MenuItem

Immutable identity, name, and price with a synchronized availability flag. The cart reads availability before adding an item, while restaurants retain control over toggling items on or off.

Restaurant

Restaurant owns menu and location. The nested Location value object gives assignment strategies a simple distance function without introducing map-provider dependencies.

Cart

Cart binds a customer to one restaurant and stores item lines. It validates quantity and availability, computes subtotal, and returns snapshots so an order is insulated from later cart edits.

Order

Order is the lifecycle aggregate. It stores the pricing quote, selected items, optional partner, timeline, and nested state objects for Placed, Accepted, Preparing, OutForDelivery, Delivered, and Cancelled.

DeliveryPartner

Tracks partner identity, location, online flag, capacity, and active orders. Assignment strategies use distanceTo and activeOrderCount, while the order releases capacity on delivery or cancellation.

AssignmentStrategy

Strategy interface with two nested implementations. NearestPartnerStrategy minimizes pickup distance; LeastBusyPartnerStrategy minimizes active orders and uses distance as a tie-breaker.

OrderService

Application facade plus nested collaborators for observers, pricing, quote, and order factory. It orchestrates placement, state transitions, partner assignment, and notification fanout.

Main

Small executable demo wiring a restaurant, menu, partners, nearest strategy, default pricing, observers, and the complete happy path from placement to delivery.

Dry Run

Sample input

Restaurant Dosa Hub has Masala Dosa at 12000 paise and Filter Coffee at 5000 paise. Cart has 2 dosas and 1 coffee. Partners: Priya is nearest with 0 active orders, Aman is farther with 0 active orders.

StepActionOrder statusPartner stateNotificationResult
1placeOrder(cart)PLACEDNo partnerCustomer + restaurant see placedQuote = 29000 + 3900 + 600 + 1450 = 34950 paise
2acceptOrder(orderId)ACCEPTEDNo partnerRestaurant acceptedState machine allows next step to prepare
3startPreparing(orderId)PREPARINGNo partnerPreparing updateOrder is now eligible for dispatch
4dispatchOrder(orderId)OUT_FOR_DELIVERYPriya active orders = 1Assigned to PriyaNearest strategy chooses Priya
5deliverOrder(orderId)DELIVEREDPriya active orders = 0Delivered updateTerminal success state

The trace shows why state and strategy are separate. The order decides whether dispatch is legal; the strategy decides which partner receives the dispatched order.

Complexity Analysis

OperationTimeSpaceNote
add item to cartO(1)O(1)Menu lookup and line merge are map operations for a single restaurant.
place orderO(I)O(I)I cart lines are copied into an immutable order snapshot and priced.
state transitionO(N)O(1)The transition is constant; notifying N observers dominates.
assign nearest partnerO(P)O(1)Scans P partners and keeps the smallest distance to the restaurant.
assign least-busy partnerO(P)O(1)Scans P partners, comparing active order count and then distance.

For interview scale, O(P) partner scans are clear and acceptable. At city scale, replace the list with a geo-index plus availability buckets while preserving the AssignmentStrategy interface.

Extensibility

New assignment policy

Add a new AssignmentStrategy such as highest-rated, zone-aware, or partner-preferred. OrderService.dispatchOrder remains unchanged.

New pricing rule

Implement PricingStrategy for surge fee, coupon discount, restaurant packaging charge, or subscription free delivery. Orders still receive a quote snapshot.

New notification channel

Implement OrderObserver for push, SMS, WhatsApp, email, restaurant dashboard, or partner app. No lifecycle code changes are required.

Scheduled or group orders

Subclass OrderFactory and override newOrder to create scheduled or group-order variants while keeping validation and pricing in the factory skeleton.

Cancellation policy

Add a policy object consulted by Order.cancel or OrderService.cancelOrder to block cancellation after partner arrival or to compute a fee.

Alternative Designs

Enum status with switch statements

Store OrderStatus directly and use switch statements in OrderService to validate transitions.

Tradeoffs

Less code for a toy solution, but transition rules leak into orchestration and become harder to extend or test as cancellation, refund, and failure states grow.

Dedicated dispatch service

Move partner assignment into a separate DispatchService that owns partner pools, geospatial indexing, retries, and reassignment after partner rejection.

Tradeoffs

Better production separation, but for an LLD interview it can distract from the core order aggregate unless the interviewer asks about scale.

Event-sourced order lifecycle

Persist events such as OrderPlaced, RestaurantAccepted, PartnerAssigned, and OrderDelivered, then rebuild current state from the event stream.

Tradeoffs

Excellent auditability and replay, but more infrastructure and eventual consistency than needed for the base object model.

Common Mistakes

  • ×

    Treating order status as a public mutable enum and allowing any service method to set it directly.

  • ×

    Putting nearest and least-busy partner matching in one long if block inside OrderService.

  • ×

    Forgetting to snapshot menu prices at order creation, so later menu edits change historical orders.

  • ×

    Not releasing a partner's active order slot after delivery or cancellation.

  • ×

    Sending notifications from inside every state class instead of through a centralized observer fanout.

  • ×

    Allowing dispatch before the restaurant has accepted and started preparing the order.

  • ×

    Mixing payment, coupon, and refund workflows into the core lifecycle before clarifying scope.

Follow-up Interview Questions

QHow would you support partner rejection after assignment?

Add an Assigned state or dispatch attempt object. If a partner rejects, release that partner, record the attempt, and ask the assignment strategy for the next candidate.

QHow would you make nearest-partner lookup faster than O(P)?

Index online partners by geohash or grid cell, then query nearby cells first. The optimization sits behind AssignmentStrategy so the order flow is unchanged.

QHow do you prevent two dispatchers from assigning two partners?

Synchronize or transact on the order row during dispatch. The check that the order is Preparing and the write to OutForDelivery plus partner assignment must be atomic.

QWhere do refunds and cancellation fees fit?

Use a cancellation policy plus payment adapter around OrderService.cancelOrder. The order marks a legal terminal state; the payment workflow handles money movement.

QHow would you notify millions of customers reliably?

Make observers publish domain events to a durable queue. Workers perform push, SMS, email, and retries with idempotency keys based on order id and status.

Production Considerations

Persistence and transactions

Persist orders, menu snapshots, partner assignments, and status events. Dispatch should use a transaction or compare-and-set from Preparing to OutForDelivery.

Geospatial matching

Store partner locations in a geospatial index and update them through a streaming channel. Strategy implementations can query nearby partners instead of scanning a list.

Notification reliability

Replace synchronous observers with durable events, retries, dead-letter queues, and idempotent notification sends.

Menu and price consistency

Cache menus carefully, but snapshot item names, quantities, and prices into the order so support and refunds use the original customer agreement.

Observability

Track order transition latency, restaurant acceptance time, partner assignment success rate, cancellation reason, and notification delivery failures.

What Interviewers Look For

  • Does the candidate make the order lifecycle explicit instead of scattering status checks?

  • Can they explain why partner matching is a Strategy and when nearest versus least-busy should be used?

  • Do they separate pricing snapshot, payment, and notification concerns?

  • Do they preserve invariants around dispatch and terminal states?

  • Can they describe the production upgrade path without overbuilding the base LLD?

Quiz

0/5 answered

  1. 1.Why use the State pattern for **Order**?

  2. 2.What does **AssignmentStrategy** make easy to change?

  3. 3.Why should price be snapshotted when the order is created?

  4. 4.Which component should send status updates to customer, restaurant, and partner channels?

  5. 5.What must be atomic during dispatch?

Practice Variants

Partner rejection and reassignment

Advanced

Add a PartnerAssigned state and allow a partner to reject. Re-run assignment while avoiding partners who already rejected the order.

Coupon and subscription pricing

Intermediate

Add pricing strategies for coupons, free-delivery subscription, and surge fee while keeping OrderFactory unchanged.

Multi-restaurant checkout

Advanced

Split one checkout into multiple restaurant-specific orders under a parent checkout id. Decide how partner assignment and cancellation work per child order.

Flashcards

Cheat Sheet

Entities: Restaurant, MenuItem, Cart, Order, DeliveryPartner, AssignmentStrategy, OrderService, OrderObserver.

Lifecycle: Placed -> Accepted -> Preparing -> OutForDelivery -> Delivered. Cancelled is terminal for allowed failure paths.

Patterns: State for lifecycle, Strategy for assignment and pricing, Observer for notifications, Factory Method for order creation.

Pricing: snapshot subtotal, delivery fee, platform fee, tax, and total at order creation.

Partner matching: nearest minimizes pickup distance; least-busy minimizes active orders and uses distance as tie-breaker.

Invariants: one restaurant per cart; non-empty cart to place order; no dispatch before preparation; no delivery before dispatch; release partner capacity on terminal state.

Scale path: persist status events, use row locks or compare-and-set for dispatch, replace partner list scan with geospatial index, publish notifications through a queue.

References