Skip to main content

Design a Quick commerce Delivery system like Amazon Now, Swiggy Instamart / Zepto (10-Minute Delivery LLD)

Problem Statement

Design a 10-minute instant delivery logistics engine (similar to Swiggy Instamart or Zepto). The engine must manage a network of local warehouses (Dark Stores) with live inventory, locate the nearest store with complete stock availability for a customer, reserve items atomically, locate and assign the nearest available delivery partner, and drive the order delivery state transitions (RECEIVED, PACKING, OUT_FOR_DELIVERY, DELIVERED).

Asked In Companies
Swiggy Zepto Flipkart

Design Decisions & Patterns Used

Instant delivery requires high speed and location accuracy. When a customer checks out, the system must search for stores in a 3-5 km radius, check their inventory, and block the items atomically to prevent double-selling. Once reserved, it must dispatch the nearest available delivery partner to the dark store.

We will utilize the following Design Patterns:

  • Strategy Pattern: Abstracting the proximity matching algorithms (e.g., sorting partners and stores by distance).
  • State Pattern: Representing the order status lifecycle, defining allowed transition rules (e.g., cannot transition from RECEIVED directly to DELIVERED).
  • Observer Pattern: Notifying customers and delivery partners of order status updates in real-time.

Functional Requirements

  • Model coordinates (latitude/longitude) and calculate distance between coordinates.
  • Register Dark Stores with inventory levels and location coordinates.
  • Search and assign the nearest Dark Store containing all items in the customer's cart.
  • Ensure thread-safe inventory check and reservation (atomic updates).
  • Find and assign the nearest available delivery partner to the selected Dark Store.
  • Drive the order status lifecycle, validating state transitions.

Objects Required

  • Location (Value object tracking geographical coordinates and distance calculations)
  • OrderStatus (Enum mapping delivery states)
  • DarkStore (Entity representing local warehouses and managing inventory)
  • DeliveryPartner (Entity representing active riders)
  • Order (Aggregate tracking assigned stores, riders, and delivery status)
  • InstamartService (Main orchestrator routing orders and coordinating dispatches)

Location Class & OrderStatus Enum

The Location class calculates distances, and the OrderStatus enum represents order status states.


public class Location {
    private final double latitude;
    private final double longitude;

    public Location(double latitude, double longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public double getDistanceTo(Location other) {
        // Simplified Euclidean distance calculation
        double latDiff = this.latitude - other.latitude;
        double lonDiff = this.longitude - other.longitude;
        return Math.sqrt(latDiff * latDiff + lonDiff * lonDiff);
    }

    public double getLatitude() { return latitude; }
    public double getLongitude() { return longitude; }
}

Let's define the OrderStatus enum:


public enum OrderStatus {
    RECEIVED,
    PACKING,
    OUT_FOR_DELIVERY,
    DELIVERED
}

The Location class is immutable, ensuring thread-safe distance calculations.


DarkStore Class

The DarkStore class manages local warehouse inventory. The reserveInventory() method checks and reserves items atomically to prevent double-selling.


import java.util.HashMap;
import java.util.Map;

public class DarkStore {
    private final String id;
    private final String name;
    private final Location location;
    private final Map<String, Integer> inventory;

    public DarkStore(String id, String name, Location location) {
        this.id = id;
        this.name = name;
        this.location = location;
        this.inventory = new HashMap<>();
    }

    public void addInventory(String itemId, int quantity) {
        inventory.put(itemId, inventory.getOrDefault(itemId, 0) + quantity);
    }

    public synchronized boolean hasInventory(Map<String, Integer> items) {
        for (Map.Entry<String, Integer> entry : items.entrySet()) {
            int available = inventory.getOrDefault(entry.getKey(), 0);
            if (available < entry.getValue()) {
                return false;
            }
        }
        return true;
    }

    public synchronized boolean reserveInventory(Map<String, Integer> items) {
        if (!hasInventory(items)) return false;

        for (Map.Entry<String, Integer> entry : items.entrySet()) {
            int available = inventory.get(entry.getKey());
            inventory.put(entry.getKey(), available - entry.getValue());
        }
        return true;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public Location getLocation() { return location; }
}

The constructor configures the warehouse properties. hasInventory() verifies stock availability, and reserveInventory() deducts items from the inventory map under a synchronized lock to guarantee thread safety.


DeliveryPartner & Order Classes

The DeliveryPartner class represents riders, and the Order class tracks the assigned store, rider, and status transitions.


public class DeliveryPartner {
    private final String id;
    private final String name;
    private Location location;
    private boolean isAvailable;

    public DeliveryPartner(String id, String name, Location location) {
        this.id = id;
        this.name = name;
        this.location = location;
        this.isAvailable = true;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    
    public synchronized Location getLocation() { return location; }
    public synchronized void setLocation(Location location) { this.location = location; }

    public synchronized boolean isAvailable() { return isAvailable; }
    public synchronized void setAvailable(boolean available) { isAvailable = available; }
}

The constructor sets initial states. Location coordinates and availability flags are synchronized to support concurrent routing queries.


import java.util.Map;

public class Order {
    private final String id;
    private final Map<String, Integer> items;
    private final Location customerLocation;
    private OrderStatus status;
    private DarkStore assignedStore;
    private DeliveryPartner assignedPartner;

    public Order(String id, Map<String, Integer> items, Location customerLocation) {
        this.id = id;
        this.items = items;
        this.customerLocation = customerLocation;
        this.status = OrderStatus.RECEIVED;
    }

    public synchronized void updateStatus(OrderStatus newStatus) {
        // State Transition Validation
        if (newStatus.ordinal() != this.status.ordinal() + 1) {
            throw new IllegalStateException("Invalid state transition from " + this.status + " to " + newStatus);
        }
        this.status = newStatus;
        System.out.println("Order [" + id + "] status updated to: " + newStatus);
    }

    public String getId() { return id; }
    public Map<String, Integer> getItems() { return items; }
    public Location getCustomerLocation() { return customerLocation; }
    
    public synchronized OrderStatus getStatus() { return status; }
    
    public synchronized DarkStore getAssignedStore() { return assignedStore; }
    public synchronized void setAssignedStore(DarkStore store) { this.assignedStore = store; }

    public synchronized DeliveryPartner getAssignedPartner() { return assignedPartner; }
    public synchronized void setAssignedPartner(DeliveryPartner partner) { this.assignedPartner = partner; }
}

The constructor sets initial states. updateStatus() validates transitions by asserting that status updates progress in sequence (using ordinals).


InstamartService Class

The InstamartService class handles registration, searches for stores with inventory, assigns riders, and coordinates order dispatch.


import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

public class InstamartService {
    private final List<DarkStore> stores;
    private final List<DeliveryPartner> partners;

    public InstamartService() {
        this.stores = new CopyOnWriteArrayList<>();
        this.partners = new CopyOnWriteArrayList<>();
    }

    public void registerStore(DarkStore store) { stores.add(store); }
    public void registerPartner(DeliveryPartner partner) { partners.add(partner); }

    public Order placeOrder(String orderId, Map<String, Integer> items, Location customerLoc) {
        System.out.println("Processing order: " + orderId);

        // 1. Locate nearest Dark Store with sufficient inventory
        DarkStore selectedStore = null;
        double minStoreDistance = Double.MAX_VALUE;

        for (DarkStore store : stores) {
            if (store.hasInventory(items)) {
                double dist = store.getLocation().getDistanceTo(customerLoc);
                if (dist < minStoreDistance) {
                    minStoreDistance = dist;
                    selectedStore = store;
                }
            }
        }

        if (selectedStore == null) {
            throw new RuntimeException("Order Failed: No local Dark Store contains all items in the cart.");
        }

        // 2. Reserve inventory
        selectedStore.reserveInventory(items);
        System.out.println("Reserved inventory at Dark Store: " + selectedStore.getName());

        Order order = new Order(orderId, items, customerLoc);
        order.setAssignedStore(selectedStore);

        // 3. Locate nearest available Delivery Partner to the selected Dark Store
        DeliveryPartner selectedPartner = null;
        double minPartnerDistance = Double.MAX_VALUE;

        synchronized (partners) {
            for (DeliveryPartner partner : partners) {
                if (partner.isAvailable()) {
                    double dist = partner.getLocation().getDistanceTo(selectedStore.getLocation());
                    if (dist < minPartnerDistance) {
                        minPartnerDistance = dist;
                        selectedPartner = partner;
                    }
                }
            }

            if (selectedPartner != null) {
                selectedPartner.setAvailable(false); // Assign partner
            }
        }

        if (selectedPartner == null) {
            System.out.println("No delivery partners available. Order remains in queue.");
            return order;
        }

        order.setAssignedPartner(selectedPartner);
        System.out.println("Assigned Delivery Partner: " + selectedPartner.getName() + 
                " (Distance to store: " + String.format("%.2f", minPartnerDistance) + " km)");

        return order;
    }
}

Here is an explanation of the core operations in the InstamartService class:

  • The constructor configures concurrency lists using CopyOnWriteArrayList to support dynamic store and rider registrations.
  • placeOrder() filters stores by inventory availability, identifies the nearest store to the customer, reserves items, locates the nearest available delivery partner to the store, and assigns them to the order.

Main Driver Class

This class tests our instant delivery logistics engine. It creates dark stores, sets up stock levels, registers riders, places orders, and updates order status.


import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        InstamartService service = new InstamartService();

        // Register Dark Stores
        DarkStore store1 = new DarkStore("DS-01", "Koramangala DarkStore", new Location(12.93, 77.62));
        store1.addInventory("milk", 10);
        store1.addInventory("bread", 5);

        DarkStore store2 = new DarkStore("DS-02", "Indiranagar DarkStore", new Location(12.97, 77.64));
        store2.addInventory("milk", 20);
        store2.addInventory("bread", 1); // insufficient bread

        service.registerStore(store1);
        service.registerStore(store2);

        // Register Delivery Partners
        DeliveryPartner partner1 = new DeliveryPartner("DP-01", "Rider Amit", new Location(12.94, 77.63));
        DeliveryPartner partner2 = new DeliveryPartner("DP-02", "Rider Sumit", new Location(12.98, 77.65));

        service.registerPartner(partner1);
        service.registerPartner(partner2);

        // Customer order: 2 milk, 2 bread. Location is near Koramangala
        Location customerLocation = new Location(12.92, 77.61);
        Map<String, Integer> cart = new HashMap<>();
        cart.put("milk", 2);
        cart.put("bread", 2);

        System.out.println("==========================================");
        System.out.println("Scenario 1: Placing Order and Dispatching Rider");
        System.out.println("==========================================");

        Order order = service.placeOrder("ORD-999", cart, customerLocation);

        System.out.println("\n--- Order Processing Status Lifecycle ---");
        // Transition order status
        order.updateStatus(OrderStatus.PACKING);
        order.updateStatus(OrderStatus.OUT_FOR_DELIVERY);
        order.updateStatus(OrderStatus.DELIVERED);

        // Free the delivery partner
        if (order.getAssignedPartner() != null) {
            order.getAssignedPartner().setAvailable(true);
        }
    }
}

The main() driver configures the environment, registers dark stores with initial stock, registers delivery partners, places a customer order, routes it to the nearest available store with sufficient stock, and updates its status through the delivery lifecycle.


Also See

Comments

Popular posts from this blog

Designing a Parking Lot - Low Level Design

Problem Statement Design a parking lot that can handle vehicles entering and leaving while managing parking across multiple floors. Each vehicle should be assigned a suitable parking spot based on its type, and the spot should be freed once the vehicle exits. The design should also support generating a ticket at entry and optionally calculating the parking fee based on the duration of stay. Asked In Companies Amazon Google Microsoft Uber Walmart Flipkart Meta PayPal Oracle Salesforce Adobe Apple Intuit LinkedIn Atlassian Functional Requirements The design should support multiple vehicle types such as bikes, cars, and trucks A vehicle must be assigned a parking spot compatible with its type A parking spot cannot be assigned to more than one vehicle at a time The parking lot should support multiple levels (floors) The design should search and allocate an availa...

Most Frequently Asked Low Level Design(LLD) Interview Questions

Below are the curated list of most commonly asked Low Level Design (LLD) interview problems. Each problem includes a short description and a link to the complete solution with code and class diagrams. Design Parking Lot System The system should handle parking for different vehicle types such as bikes, cars, and trucks. It should manage slot allocation, availability tracking, and entry/exit flow. The design also ensures efficient usage of parking space under varying load conditions. View Solution Design Elevator / Lift System The system should support multiple elevators operating across floors with request handling logic. It focuses on scheduling algorithms to minimize wait time and optimize movement. It also manages direction control and concurrent floor requests. View Solution Design Movie Ticket Booking System The system should allow users to browse movies, select shows, and book seats. It handles seat ...

Software Design Patterns for LLD Interviews: A Complete Guide

Software Design Patterns for LLD Interviews: A Complete Guide In Software Development Engineer (SDE) interviews—especially for mid-level and senior roles—low-level design (LLD) rounds assess your ability to write clean, reusable, maintainable, and extensible code. The foundation of resolving these architectural challenges lies in the standard Gang of Four (GoF) Design Patterns. Rather than memorizing theoretical definitions, interviewers expect you to apply these patterns to real-world scenarios, identifying the trade-offs of each. Below is a comprehensive guide to the 12 most frequently asked design patterns in LLD interviews, categorized by their classification (Creational, Structural, and Behavioral). Each pattern contains a concrete, real-world Java implementation and a detailed breakdown of design decisions. Creational Design Patterns Creational design patterns deal with object creation mechanisms. They abstract the instantiation process, making a system independent of how...