CASE STUDY

Food Ordering System (Machine Coding / LLD)

3 min read·490 words·Intermediate

Asked at

2 candidate reports between Oct 2025 and Jul 2026

How to use this case study

SDE-2 / Mid

Build clean classes for restaurants, menus, orders and order status, with a restaurant-selection strategy, in working code within ~90 minutes.

SDE-3 / Senior

Handle concurrency (restaurant capacity, simultaneous orders and status updates), use the right patterns (strategy, state, observer) and explain SOLID choices.

Staff / Principal

Discuss how the in-memory design maps to a production service (persistence, events, idempotency) and extension points.


0) Problem Restatement

A common machine coding round (Flipkart and Cleartrip): build a simplified Swiggy/Zomato in about 90 minutes, as working code with clean object-oriented design. Typical requirements:

  • Onboard restaurants with a menu (item, price) and a max number of orders they can process at once (capacity).
  • Update menus (add items, change prices).
  • A user places an order with several items. The system selects a restaurant that has all the items and free capacity, using a strategy such as lowest total price or highest rating.
  • Restaurants mark orders as completed, which frees capacity.
  • Follow-up round: concurrency. Two orders racing for the last capacity slot, and thread-safe status updates.


1) Entities and Patterns

Architecture Diagram

classDiagram
    class Restaurant {
        +String id
        +String name
        +double rating
        +int capacity
        +int activeOrders
        +Map menu
        +tryReserve() bool
        +release() void
    }
    class Order {
        +String id
        +String userId
        +Map items
        +Restaurant restaurant
        +OrderStatus status
        +int totalCents
    }
    class SelectionStrategy {
        <<interface>>
        +select(candidates, items) Restaurant
    }
    class LowestPriceStrategy
    class HighestRatingStrategy
    class OrderService {
        +placeOrder(userId, items, strategy) Order
        +completeOrder(orderId) void
    }
    class OrderObserver {
        <<interface>>
        +onStatusChange(order) void
    }
    SelectionStrategy <|.. LowestPriceStrategy
    SelectionStrategy <|.. HighestRatingStrategy
    OrderService --> SelectionStrategy
    OrderService --> Restaurant
    OrderService --> Order
    OrderService --> OrderObserver
  • Strategy pattern for restaurant selection: new rules (fastest, nearest) are new classes, with no edits to OrderService.
  • State of an order as an enum with allowed transitions: PLACED → ACCEPTED → PREPARING → OUT_FOR_DELIVERY → DELIVERED, plus CANCELLED. Invalid moves throw.
  • Observer for notifications: the user, the restaurant dashboard and the logger subscribe to status changes.


2) Code (Python)

import threading, uuid
from enum import Enum

class Status(Enum):
    PLACED = 1; ACCEPTED = 2; PREPARING = 3; OUT_FOR_DELIVERY = 4; DELIVERED = 5; CANCELLED = 6

ALLOWED = {
    Status.PLACED: {Status.ACCEPTED, Status.CANCELLED},
    Status.ACCEPTED: {Status.PREPARING, Status.CANCELLED},
    Status.PREPARING: {Status.OUT_FOR_DELIVERY},
    Status.OUT_FOR_DELIVERY: {Status.DELIVERED},
}

class Restaurant:
    def __init__(self, name, capacity, rating, menu):
        self.id, self.name, self.capacity, self.rating = str(uuid.uuid4()), name, capacity, rating
        self.menu = dict(menu)                 # item -> price in cents
        self.active = 0
        self.lock = threading.Lock()

    def has_all(self, items):
        return all(i in self.menu for i in items)

    def price(self, items):
        return sum(self.menu[i] * q for i, q in items.items())

    def try_reserve(self):
        with self.lock:                         # atomic check-and-increment
            if self.active < self.capacity:
                self.active += 1
                return True
            return False

    def release(self):
        with self.lock:
            self.active -= 1

class LowestPrice:
    def rank(self, candidates, items):
        return sorted(candidates, key=lambda r: (r.price(items), r.name))

class HighestRating:
    def rank(self, candidates, items):
        return sorted(candidates, key=lambda r: (-r.rating, r.name))

class Order:
    def __init__(self, user, items, restaurant):
        self.id, self.user, self.items, self.restaurant = str(uuid.uuid4()), user, items, restaurant
        self.total = restaurant.price(items)
        self.status = Status.PLACED
        self.lock = threading.Lock()

class OrderService:
    def __init__(self):
        self.restaurants, self.orders, self.observers = [], {}, []

    def add_restaurant(self, r): self.restaurants.append(r)

    def place_order(self, user, items, strategy):
        candidates = [r for r in self.restaurants if r.has_all(items)]
        for r in strategy.rank(candidates, items):   # best first, fall back if full
            if r.try_reserve():
                order = Order(user, items, r)
                self.orders[order.id] = order
                self._notify(order)
                return order
        raise RuntimeError("No restaurant can take this order right now")

    def update_status(self, order_id, new_status):
        order = self.orders[order_id]
        with order.lock:
            if new_status not in ALLOWED.get(order.status, set()):
                raise ValueError(f"{order.status.name} -> {new_status.name} not allowed")
            order.status = new_status
        if new_status in (Status.DELIVERED, Status.CANCELLED):
            order.restaurant.release()           # free capacity
        self._notify(order)

    def _notify(self, order):
        for obs in self.observers: obs(order)

3) Concurrency Follow-up Explained

  • Race on the last slot: two threads see active = capacity - 1 and both increment → over capacity. try_reserve() does check and increment under one lock, so only one wins. The loser falls back to the next-best restaurant.
  • Status updates: a per-order lock plus the allowed-transitions table stops two threads from applying conflicting changes (e.g., cancel and accept at once).
  • Release exactly once: capacity is released only on the transition into DELIVERED or CANCELLED, which can happen once because of the state rules.
  • No lock-ordering deadlocks: we never hold two restaurant locks at the same time.


4) SOLID and Extensibility (what reviewers ask)

  • Single responsibility: Restaurant manages its own menu and capacity, OrderService orchestrates, and strategies only rank.
  • Open/closed: add a FastestDeliveryStrategy without touching existing code.
  • Dependency inversion: OrderService depends on the strategy interface, not concrete classes.
  • Extensions: menu updates (lock per restaurant), ratings updates, splitting one order across restaurants, persistence via a repository interface, and idempotent place_order with a client request ID.


5) Mapping to a Real Service

In production: restaurants and orders live in a DB, capacity is reserved with an atomic conditional update (UPDATE ... SET active = active + 1 WHERE active < capacity), status changes are events, and observers become consumers (notifications, analytics).


6) Wrap-Up

Model restaurants (menu, capacity with an atomic reserve and release), orders (items, total, status with an explicit transition table) and an order service that ranks candidate restaurants through a pluggable strategy and falls back when one is full. Handle concurrency with check-and-increment under a lock and per-order locks for status, notify through observers, and keep the design SOLID so new strategies and features plug in cleanly.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →