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
| Page | Strategy | Why |
|---|---|---|
| Restaurant List | SSR + ISR | SEO, fast first paint, data changes hourly |
| Restaurant Menu | SSR with client revalidation | SEO for restaurant pages, prices change frequently |
| Cart | Client-only | User-specific, no SEO value |
| Order Tracking | Client-only + WebSocket | Real-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=true3. 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
- Skeleton screens for restaurant cards and menu items
- Image optimization with blur-up placeholder pattern
- Route-based code splitting â Menu page JS not loaded on list page
- Prefetch restaurant data on card hover
- Optimistic cart updates â UI updates immediately, syncs with server async
- 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>
);
}