LLD Hub
lldobserverfactorystrategy

Food Delivery System Low Level Design (Swiggy/Zomato) — LLD Guide

Design Swiggy or Zomato's core system — restaurant browse, order placement, delivery partner assignment, and real-time tracking. Frequently asked at Swiggy interviews.

7 April 2025·9 min read

Practice this problem

Food Delivery System (Swiggy/Zomato) — get AI-scored feedback on your solution

Solve it →

Food Delivery System (Swiggy / Zomato) is one of the most frequently asked Low Level Design problems in product-based company interviews. It covers restaurant browsing, order placement, real-time delivery partner assignment, and order tracking. Frequently asked at Swiggy, Zomato, Dunzo, and Flipkart, this guide covers the complete LLD with Java code, class diagram, and FAQ.

Why Interviewers Ask Food Delivery LLD

This problem combines multiple design challenges in one. Interviewers want to see:

  • Can you model the order lifecycle as a state machine (PLACED → CONFIRMED → DELIVERED)?
  • Do you use Observer pattern to notify users of real-time order status updates?
  • Can you design a delivery partner assignment strategy (nearest available, load-balanced)?
  • Do you separate RestaurantService, OrderService, and DeliveryService cleanly (SRP)?
  • Can you handle concurrent order placement without overselling menu items?

Functional Requirements

  • Users can browse restaurants by city, cuisine, and rating
  • Users can view a restaurant menu and add items to cart
  • Users can place an order — triggers restaurant confirmation and delivery assignment
  • Order lifecycle: PLACED → ACCEPTED → PREPARING → PICKED_UP → DELIVERED
  • Real-time order tracking — user sees delivery partner location
  • Delivery partner assignment — nearest available partner gets the order
  • Users can rate the restaurant and delivery partner post-delivery
  • Support promo codes and discounts

Non-Functional Requirements

  • Order status updates must reach the user within 2 seconds
  • Delivery assignment must complete in under 5 seconds
  • System must handle 10,000 concurrent orders during peak hours
  • Restaurant menu updates (price, availability) must propagate without restart

Core Entities — Food Delivery LLD Class Design

  • User — id, name, email, savedAddresses
  • Restaurant — id, name, city, cuisine, rating, menu
  • MenuItem — id, name, price, category, isAvailable
  • Cart — userId, restaurantId, list of CartItems
  • Order — id, user, restaurant, items, deliveryAddress, status, totalAmount
  • DeliveryPartner — id, name, currentLocation, status (AVAILABLE/ON_TRIP)
  • Delivery — id, orderId, partner, pickupTime, deliveryTime, status
  • AssignmentStrategy — interface; NearestPartnerStrategy implements it
  • NotificationService — Observer-based; pushes status updates to users

Text-Based Class Diagram

User
+-- id, name, email
+-- savedAddresses: List<Address>

Restaurant
+-- id, name, city, cuisine
+-- rating: double, menu: List<MenuItem>
+-- isOpen: boolean

MenuItem
+-- id, name, price: double
+-- category: String, isAvailable: boolean

Order
+-- id, user: User, restaurant: Restaurant
+-- items: List<OrderItem>
+-- deliveryAddress: Address
+-- status: OrderStatus
+-- totalAmount: double
+-- promoCode: String (nullable)

DeliveryPartner
+-- id, name, phone
+-- location: GeoPoint
+-- status: PartnerStatus (AVAILABLE/ON_TRIP)

Delivery
+-- id, orderId: String
+-- partner: DeliveryPartner
+-- pickupTime, deliveryTime: LocalDateTime
+-- status: DeliveryStatus

AssignmentStrategy (interface)
+-- assign(order, partners): DeliveryPartner

NearestPartnerStrategy implements AssignmentStrategy

Order State Machine — Java

public enum OrderStatus {
    PLACED, ACCEPTED, PREPARING, PICKED_UP, DELIVERED, CANCELLED
}

public class OrderStateMachine {
    private static final Map<OrderStatus, Set<OrderStatus>> VALID_TRANSITIONS = new EnumMap<>(OrderStatus.class);

    static {
        VALID_TRANSITIONS.put(OrderStatus.PLACED,    Set.of(OrderStatus.ACCEPTED, OrderStatus.CANCELLED));
        VALID_TRANSITIONS.put(OrderStatus.ACCEPTED,  Set.of(OrderStatus.PREPARING, OrderStatus.CANCELLED));
        VALID_TRANSITIONS.put(OrderStatus.PREPARING, Set.of(OrderStatus.PICKED_UP));
        VALID_TRANSITIONS.put(OrderStatus.PICKED_UP, Set.of(OrderStatus.DELIVERED));
        VALID_TRANSITIONS.put(OrderStatus.DELIVERED, Set.of());
        VALID_TRANSITIONS.put(OrderStatus.CANCELLED, Set.of());
    }

    public static void transition(Order order, OrderStatus newStatus) {
        Set<OrderStatus> allowed = VALID_TRANSITIONS.get(order.getStatus());
        if (allowed == null || !allowed.contains(newStatus)) {
            throw new InvalidOrderTransitionException(
                "Cannot transition from " + order.getStatus() + " to " + newStatus);
        }
        order.setStatus(newStatus);
    }
}

Delivery Partner Assignment

public interface AssignmentStrategy {
    DeliveryPartner assign(Order order, List<DeliveryPartner> availablePartners);
}

public class NearestPartnerStrategy implements AssignmentStrategy {
    @Override
    public DeliveryPartner assign(Order order, List<DeliveryPartner> availablePartners) {
        GeoPoint restaurantLocation = order.getRestaurant().getLocation();

        return availablePartners.stream()
            .filter(p -> p.getStatus() == PartnerStatus.AVAILABLE)
            .min(Comparator.comparingDouble(
                p -> haversineDistance(p.getLocation(), restaurantLocation)
            ))
            .orElseThrow(() -> new NoPartnerAvailableException("No delivery partners available"));
    }

    private double haversineDistance(GeoPoint a, GeoPoint b) {
        double R = 6371; // Earth radius km
        double dLat = Math.toRadians(b.getLat() - a.getLat());
        double dLon = Math.toRadians(b.getLon() - a.getLon());
        double x = Math.sin(dLat/2) * Math.sin(dLat/2)
                 + Math.cos(Math.toRadians(a.getLat())) * Math.cos(Math.toRadians(b.getLat()))
                 * Math.sin(dLon/2) * Math.sin(dLon/2);
        return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x));
    }
}

OrderService and Observer-Based Notifications

public interface OrderStatusObserver {
    void onStatusChange(Order order, OrderStatus newStatus);
}

public class PushNotificationObserver implements OrderStatusObserver {
    private final PushService pushService;
    @Override
    public void onStatusChange(Order order, OrderStatus newStatus) {
        String msg = buildMessage(newStatus, order.getRestaurant().getName());
        pushService.send(order.getUser().getId(), msg);
    }
}

public class OrderService {
    private final OrderRepository orderRepo;
    private final DeliveryService deliveryService;
    private final List<OrderStatusObserver> observers = new ArrayList<>();

    public void addObserver(OrderStatusObserver observer) { observers.add(observer); }

    public Order placeOrder(OrderRequest req, String userId) {
        validateCart(req);
        Order order = new Order(UUID.randomUUID().toString(),
            userRepo.findById(userId), restaurantRepo.findById(req.getRestaurantId()),
            req.getItems(), req.getDeliveryAddress(), OrderStatus.PLACED,
            calculateTotal(req));
        orderRepo.save(order);
        notifyObservers(order, OrderStatus.PLACED);

        // Async: restaurant confirmation (webhook or polling)
        restaurantService.notifyRestaurant(order);
        return order;
    }

    public void updateStatus(String orderId, OrderStatus newStatus) {
        Order order = orderRepo.findById(orderId);
        OrderStateMachine.transition(order, newStatus);
        orderRepo.save(order);
        notifyObservers(order, newStatus);

        if (newStatus == OrderStatus.ACCEPTED) {
            deliveryService.assignPartner(order);
        }
    }

    private void notifyObservers(Order order, OrderStatus status) {
        observers.forEach(obs -> obs.onStatusChange(order, status));
    }
}

Key Design Decisions

  • State machine with explicit transition map: The VALID_TRANSITIONS map prevents invalid state changes (e.g., DELIVERED → PLACED) without a chain of if-else blocks. Adding a new state (e.g., RETURNED) requires only adding an entry to the map.
  • Observer for status notifications: Multiple consumers need to react to order status changes — push notifications, email, analytics, delivery assignment. Observer decouples OrderService from each consumer. Adding an SMS observer is a new class only.
  • Delivery assignment as a separate service: DeliveryService is only triggered on the ACCEPTED transition. If assignment fails (no partners available), the order stays in ACCEPTED state and retries — it does not pollute OrderService logic.
  • Haversine for distance: Great-circle distance is the correct metric for partner assignment. Euclidean distance is wrong at city scale because it does not account for Earth curvature. Haversine adds 20 lines and is the industry standard.

Common Follow-Up Questions

  • "How do you handle a restaurant rejecting an order?" — Restaurant sends a CANCELLED event. OrderService transitions to CANCELLED, triggers refund, and notifies the user. The transition map explicitly allows ACCEPTED → CANCELLED.
  • "How do you implement promo codes?" — Add a PromoService with validatePromo (userId, code, orderAmount). Returns a DiscountResult with discount type (FLAT/PERCENT) and value. OrderService applies the discount before saving the total.
  • "How do you track the delivery partner live?" — Partner app sends GPS pings every 5 seconds to a location service. The API returns the partner's current GeoPoint. The client polls or uses WebSocket to display live tracking.

FAQ — Food Delivery System Low Level Design

What design patterns are used in Swiggy/Zomato LLD?

The primary patterns are Observer (order status notifications),Strategy (delivery partner assignment), State Machine (order lifecycle), and Factory (creating order objects from request DTOs).

How do you design delivery partner assignment in food delivery LLD?

The NearestPartnerStrategy filters all AVAILABLE partners, computes Haversine distance from the restaurant, and picks the closest one. Mark that partner as ON_TRIP atomically to prevent double assignment. For scale, maintain a geospatial index (Redis GEO or PostGIS) to find nearby partners in O(log n) instead of scanning all partners.

What is the order state machine in food delivery?

States: PLACED (user submitted) → ACCEPTED (restaurant confirmed) → PREPARING (kitchen cooking) → PICKED_UP (partner collected) → DELIVERED. Cancellation is allowed from PLACED and ACCEPTED. Any invalid transition (e.g., PREPARING → PLACED) throws an exception.

How do you handle concurrent orders from the same restaurant?

Add a capacity check in OrderService — each restaurant has a maxConcurrentOrders setting. Use an atomic counter per restaurant. If the counter exceeds capacity, reject with a service unavailable response. Reset the counter when an order moves to PICKED_UP or CANCELLED.

Ready to practice?

Submit your solution and get AI-scored feedback on OOP, SOLID principles, design patterns, and code quality.

Solve Food Delivery System (Swiggy/Zomato)