Home/Learn/Low Level Design/Design a Food Delivery App like Swiggy

Design a Food Delivery App like Swiggy

Advanced
LLD Interview Problems
Java Source Code

Model a food delivery platform with restaurant ordering, order status machine, delivery agent assignment, and ETA calculation using Strategy and Observer.

Overview

A Food Delivery App connects Customers, Restaurants, and DeliveryAgents. Restaurant publishes a Menu of MenuItems. Customer places an Order containing OrderItems; the Order progresses through a status machine: PLACED → ACCEPTED → PREPARING → OUT_FOR_DELIVERY → DELIVERED (or CANCELLED). Observer notifies the Customer on every transition. DeliveryAssignment service picks the nearest available agent using a Strategy. ETACalculator estimates arrival time from distance and traffic. Chain of Responsibility models the order processing pipeline: validation → inventory check → payment → assignment. FoodDeliveryApp is the Facade coordinating all subsystems.

Requirements Analysis

Functional: browse restaurant menu, place order, restaurant accepts/prepares order, assign delivery agent, track delivery, deliver and rate. Non-functional: Observer-driven status updates decoupled from business logic, delivery assignment strategy swappable, order pipeline extensible via Chain of Responsibility.

Requirements
// Entities : FoodDeliveryApp, Restaurant, Menu, MenuItem, Order, OrderItem, OrderStatus, DeliveryAgent
// Patterns : Observer (status), Strategy (assignment + ETA), Chain of Responsibility (order pipeline)

Core Classes & Relationships

OrderStatus enum: PLACED, ACCEPTED, PREPARING, OUT_FOR_DELIVERY, DELIVERED, CANCELLED. MenuItem holds name, price, and availability. Order aggregates OrderItems and status. DeliveryAssignmentStrategy selects a DeliveryAgent. OrderStatusObserver fires on each transition. ETACalculator interface has estimateMinutes(Location from, Location to).

Java — enums & interfaces
public enum OrderStatus {
    PLACED, ACCEPTED, PREPARING, OUT_FOR_DELIVERY, DELIVERED, CANCELLED
}

public class MenuItem {
    private final String itemId;
    private final String name;
    private final double price;
    private boolean available = true;

    public MenuItem(String itemId, String name, double price) {
        this.itemId = itemId; this.name = name; this.price = price;
    }
    public String getItemId()      { return itemId; }
    public double getPrice()       { return price; }
    public boolean isAvailable()   { return available; }
    public void setAvailable(boolean a) { this.available = a; }
}

public interface DeliveryAssignmentStrategy {
    Optional<DeliveryAgent> assign(List<DeliveryAgent> agents, Location restaurantLocation);
}

public interface OrderStatusObserver {
    void onStatusChange(String orderId, OrderStatus newStatus);
}

public interface ETACalculator {
    int estimateMinutes(Location from, Location to);
}

Java Implementation

Order enforces status transitions and fires observers. Restaurant.placeOrder() validates items and creates the Order. DeliveryAssignment picks the nearest available agent. FoodDeliveryApp.acceptOrder() and dispatchOrder() drive the state machine. The simple NearestAgentStrategy filters available agents and selects by minimum distance.

Java — core classes
public class OrderItem {
    private final MenuItem item;
    private final int quantity;
    public OrderItem(MenuItem item, int qty) { this.item = item; this.quantity = qty; }
    public double subtotal() { return item.getPrice() * quantity; }
    public MenuItem getItem() { return item; }
}

public class Order {
    private final String orderId;
    private final List<OrderItem> items;
    private final String customerId;
    private final String restaurantId;
    private OrderStatus status = OrderStatus.PLACED;
    private DeliveryAgent assignedAgent;
    private final List<OrderStatusObserver> observers = new ArrayList<>();

    public Order(String orderId, List<OrderItem> items, String customerId, String restaurantId) {
        this.orderId = orderId; this.items = items;
        this.customerId = customerId; this.restaurantId = restaurantId;
    }
    public void addObserver(OrderStatusObserver o) { observers.add(o); }

    public void transition(OrderStatus next) {
        this.status = next;
        observers.forEach(o -> o.onStatusChange(orderId, next));
    }
    public double total()            { return items.stream().mapToDouble(OrderItem::subtotal).sum(); }
    public OrderStatus getStatus()   { return status; }
    public String getOrderId()       { return orderId; }
    public void setAgent(DeliveryAgent a) { this.assignedAgent = a; }
}

public class DeliveryAgent {
    private final String agentId;
    private final String name;
    private Location location;
    private boolean available = true;

    public DeliveryAgent(String agentId, String name, Location location) {
        this.agentId = agentId; this.name = name; this.location = location;
    }
    public boolean isAvailable()    { return available; }
    public void setAvailable(boolean a) { this.available = a; }
    public Location getLocation()   { return location; }
    public String getAgentId()      { return agentId; }
}

public class NearestAgentStrategy implements DeliveryAssignmentStrategy {
    @Override
    public Optional<DeliveryAgent> assign(List<DeliveryAgent> agents, Location restaurantLocation) {
        return agents.stream()
            .filter(DeliveryAgent::isAvailable)
            .min(Comparator.comparingDouble(a -> a.getLocation().distanceTo(restaurantLocation)));
    }
}

public class Restaurant {
    private final String restaurantId;
    private final String name;
    private final List<MenuItem> menu = new ArrayList<>();
    private final Location location;
    private int orderCounter = 0;

    public Restaurant(String restaurantId, String name, Location location) {
        this.restaurantId = restaurantId; this.name = name; this.location = location;
    }
    public void addMenuItem(MenuItem item) { menu.add(item); }
    public Location getLocation()          { return location; }

    public Order placeOrder(String customerId, Map<String, Integer> itemQtyMap) {
        List<OrderItem> items = itemQtyMap.entrySet().stream().map(e -> {
            MenuItem mi = menu.stream().filter(m -> m.getItemId().equals(e.getKey()) && m.isAvailable())
                .findFirst().orElseThrow(() -> new IllegalStateException("Item unavailable: " + e.getKey()));
            return new OrderItem(mi, e.getValue());
        }).collect(Collectors.toList());
        return new Order("ORD-" + restaurantId + "-" + (++orderCounter), items, customerId, restaurantId);
    }
}

Key Points to Remember

  • 1OrderStatus state machine prevents illegal transitions — a CANCELLED order cannot move to DELIVERED; enforce with explicit allowed-transition sets.
  • 2DeliveryAssignmentStrategy decouples agent selection logic — swapping NearestAgent for HighestRatedAgent requires zero changes to Order.
  • 3Observer on Order keeps the customer notification layer completely separate from order processing, enabling multi-channel delivery.
  • 4Chain of Responsibility for the order processing pipeline makes adding a fraud-check step a new handler with zero modification to existing handlers.

Interview Questions

Sign in to ask Aria
1

How would you handle a delivery agent going offline mid-delivery and reassigning the order?

HardSwiggy
2

How would you compute and display real-time ETA updates to the customer?

MediumUber
3

How would you design restaurant-level rate limiting to prevent order flooding during peak hours?

HardAmazon

Ask Aria about Design a Food Delivery App like Swiggy

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…