Challenges🏛ïļ ArchitectureFood Ordering App Architecture
👑ApexSystem DesignE-CommerceArchitectureState Management

Food Ordering App Architecture

Design a food ordering application with search, sort, filter, cart management, and real-time order tracking — covering state management, API design, and offline resilience.

Food Ordering App Architecture

Challenge: "Design a food ordering app like Swiggy/DoorDash. It should support restaurant browsing, menu viewing, cart management, and order tracking."

Requirements

Functional

  • Restaurant listing with search, sort (rating, distance, delivery time), and filter (cuisine, price range, veg/non-veg)
  • Restaurant menu with categories
  • Cart with add, remove, quantity adjustment, price calculation
  • Checkout flow with address selection and payment
  • Real-time order status tracking
  • Order history

Non-Functional

  • Sub-2s initial load (restaurant list)
  • Cart state persisted across sessions
  • Works on slow 3G networks (progressive loading)
  • Accessible, mobile-first responsive design

High-Level Architecture

┌─────────────────────────────────────────────────┐
│                 App Shell (SSR)                  │
├──────────┮────────────────────┮─────────────────â”Ī
│ Location │     Search Bar     │  Cart Badge     │
├──────────â”ī────────────────────â”ī─────────────────â”Ī
│                                                  │
│  ┌─ Restaurant List (SSR + Client Hydration) ──┐│
│  │  Sort: Rating | Distance | Time             ││
│  │  Filter: Cuisine | Price | Veg              ││
│  │  ┌────────┐ ┌────────┐ ┌────────┐         ││
│  │  │ Card   │ │ Card   │ │ Card   │         ││
│  │  └────────┘ └────────┘ └────────┘         ││
│  └─────────────────────────────────────────────┘│
│                                                  │
│  ┌─ Restaurant Detail (Dynamic) ───────────────┐│
│  │  Menu Categories → Items → Add to Cart      ││
│  └─────────────────────────────────────────────┘│
│                                                  │
│  ┌─ Cart Sidebar / Page ───────────────────────┐│
│  │  Items → Quantity → Subtotal → Checkout     ││
│  └─────────────────────────────────────────────┘│
│                                                  │
│  ┌─ Order Tracking (WebSocket) ────────────────┐│
│  │  Status → Map → ETA → Delivery Updates      ││
│  └─────────────────────────────────────────────┘│
└─────────────────────────────────────────────────┘

State Architecture

interface AppState {
  location: { lat: number; lng: number; address: string };
  restaurants: {
    items: Restaurant[];
    loading: boolean;
    filters: FilterState;
    sort: SortOption;
    page: number;
    hasMore: boolean;
  };
  cart: {
    restaurantId: string | null;
    items: CartItem[];
  };
  order: {
    current: Order | null;
    history: Order[];
  };
}
 
interface CartItem {
  menuItemId: string;
  name: string;
  price: number;
  quantity: number;
  customizations: Customization[];
}

Cart State Management

The cart is the most complex client-side state:

function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case 'ADD_ITEM': {
      if (state.restaurantId && state.restaurantId !== action.restaurantId) {
        return state;
      }
      const existing = state.items.find(i => i.menuItemId === action.item.menuItemId);
      if (existing) {
        return {
          ...state,
          items: state.items.map(i =>
            i.menuItemId === action.item.menuItemId
              ? { ...i, quantity: i.quantity + 1 }
              : i
          ),
        };
      }
      return {
        restaurantId: action.restaurantId,
        items: [...state.items, { ...action.item, quantity: 1 }],
      };
    }
 
    case 'REMOVE_ITEM': {
      const items = state.items
        .map(i => i.menuItemId === action.menuItemId
          ? { ...i, quantity: i.quantity - 1 }
          : i
        )
        .filter(i => i.quantity > 0);
      return {
        restaurantId: items.length ? state.restaurantId : null,
        items,
      };
    }
 
    case 'CLEAR': return { restaurantId: null, items: [] };
  }
}

Key Design Decisions

1. Rendering Strategy

PageStrategyWhy
Restaurant ListSSR + ISRSEO, fast first paint, data changes hourly
Restaurant MenuSSR with client revalidationSEO for restaurant pages, prices change frequently
CartClient-onlyUser-specific, no SEO value
Order TrackingClient-only + WebSocketReal-time updates

2. Search, Sort & Filter

URL-driven state for shareability and back/forward navigation:

/restaurants?q=pizza&sort=rating&cuisine=italian&price=2&veg=true

3. Real-Time Order Tracking

function useOrderTracking(orderId: string) {
  const [status, setStatus] = useState<OrderStatus>('placed');
  const [eta, setEta] = useState<number | null>(null);
 
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/orders/${orderId}/track`);
 
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setStatus(data.status);
      setEta(data.estimatedMinutes);
    };
 
    return () => ws.close();
  }, [orderId]);
 
  return { status, eta };
}

4. Offline Resilience

  • Cart persisted to localStorage (survives refresh/close)
  • Restaurant list cached in Service Worker (stale-while-revalidate)
  • Failed order submission queued for background sync

Performance Optimizations

  1. Skeleton screens for restaurant cards and menu items
  2. Image optimization with blur-up placeholder pattern
  3. Route-based code splitting — Menu page JS not loaded on list page
  4. Prefetch restaurant data on card hover
  5. Optimistic cart updates — UI updates immediately, syncs with server async
  6. Virtual list for long restaurant menus (100+ items)

Cross-Restaurant Cart Conflict

When a user has items from Restaurant A and tries to add from Restaurant B:

function CartConflictDialog({ onConfirm, onCancel }) {
  return (
    <Dialog>
      <p>Your cart has items from another restaurant. Clear cart and add new item?</p>
      <button onClick={onConfirm}>Clear & Add</button>
      <button onClick={onCancel}>Keep Current Cart</button>
    </Dialog>
  );
}