Problem 4.10 — Food Delivery (LLD)
In one line
Order lifecycle state machine + delivery-partner matching + notifications — the LLD version of the classic HLD.
Think of it like this
Ordering food on Swiggy or Zomato. Your order moves through clear stages (placed, being cooked, picked up, delivered), and behind the scenes the app is also picking which delivery partner should get the order.
Key ideas
- 01
Order states: PLACED → ACCEPTED → PREPARING → READY → PICKED_UP → DELIVERED (plus CANCELLED/RETURNED and timeouts).
- 02
State machine with explicit allowed transitions and timers (prep timeout → escalation).
- 03
Matching: partner availability pool + dispatch strategy (nearest, least-loaded, rating-weighted).
- 04
Notifications are observers on order-state events (customer SMS, restaurant push, partner ping).
- 05
Delay tracking: promised vs actual timestamps per stage — data feeding the HLD's monitoring story.
- 06
Concurrency: one order → many components reading/writing — guard state transitions with versioning.
Java / Spring map
- →
Order entity with an enum state + transition guard table; Spring @Transactional state transitions.
Code & diagrams
A transition table so illegal moves are rejected in one place, and transitions are idempotent for retries.
public enum OrderStatus { PLACED, ACCEPTED, PREPARING, READY, PICKED_UP, DELIVERED, CANCELLED }
public final class OrderStateMachine {
// from → set of allowed "to" states. Anything not listed is illegal.
private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = Map.of(
OrderStatus.PLACED, EnumSet.of(OrderStatus.ACCEPTED, OrderStatus.CANCELLED),
OrderStatus.ACCEPTED, EnumSet.of(OrderStatus.PREPARING, OrderStatus.CANCELLED),
OrderStatus.PREPARING, EnumSet.of(OrderStatus.READY, OrderStatus.CANCELLED),
OrderStatus.READY, EnumSet.of(OrderStatus.PICKED_UP),
OrderStatus.PICKED_UP, EnumSet.of(OrderStatus.DELIVERED)
);
public static void transition(Order order, OrderStatus to) {
if (order.status() == to) return; // IDEMPOTENT: retrying "mark DELIVERED" is a no-op
Set<OrderStatus> allowed = TRANSITIONS.getOrDefault(order.status(), Set.of());
if (!allowed.contains(to))
throw new IllegalStateException("cannot go " + order.status() + " -> " + to);
order.setStatus(to); // @Transactional + optimistic @Version at the caller
}
}
// Why idempotency matters: a delivery partner's app retries "DELIVERED" on flaky network.
// Without the equality check above, a second racing update could overwrite audit timestamps
// or double-fire the "order delivered" notification event.Explain without notes
Which transitions must be idempotent and why (retry: 'mark DELIVERED' called twice)?
Practice
Draw the transition table with invalid moves, then implement notify-on-transition with Spring events.
Trade-offs
- ↔
Interleaving stage updates without a lock → lost updates. State transitions need row versioning.
Completion checklist
I can list the order lifecycle and the guard for every arrow.