?

Boss43 minIntro+60 XP

Food Delivery Order System.

A Complete System Design Interview Integrating All Concepts

6 stages9 code

Boss · W06

Fight it like the real interview: each phase runs against its time budget, you write your answer in a scratchpad first, then reveal the model answer side by side and grade yourself.

or read the model answer below
watch it first · newThe whole lesson in a 27-minute animated lecture5 quiz checkpoints · ask the AI about any moment · captions, chapters and speed

Intro · 1 min

The Interview Begins#

You walk into the interview room at a food delivery company. The interviewer smiles and gestures to the whiteboard.

Interviewer

"Thanks for coming in today. We're going to work through a system design problem together. I'm interested in your thought process, so please think out loud. Feel free to ask questions — this is collaborative."

They write on the whiteboard:

Design a Food Delivery Order System

We're building the core ordering platform for a food delivery service like DoorDash or Uber Eats.

Key capabilities needed:

  • Customers browse restaurants and place orders
  • Orders are routed to restaurants for preparation
  • Drivers are assigned and tracked in real-time
  • All parties receive status updates throughout
  • Payments are processed reliably
Interviewer

"Take a moment to think about this. We have about 45 minutes. Where would you like to start?"

ExplainerThe whole capstone: a food delivery order system

Requirements: 5M orders a day, restaurants seeing orders within 2 seconds, drivers assigned within 30 seconds, locations every 3 to 5 seconds, live tracking pushed while the app is open and push notifications when it's closed, Stripe authorization at order and capture at pickup. The estimate gives ~230 orders/sec at a 4x peak, ~400K active orders and ~600K live connections on ~5 gateway nodes (~20,000 heartbeats, ~100,000 coalesced location frames and ~1,000 sequenced status changes a second). The design adds a real-time tier and the notification platform's three lanes and inbox to the APIs, Kafka and workers; the deep dives cover the outbox and idempotency, row-locked assignment and the saga, live tracking with resume after a network change, and the restaurant socket and driver presence; at 10x it shards by city and grows to ~45 gateway nodes.

Phase 1 · Stage 1 of 6 · 3 min

Phase 1: Requirements Clarification#

Before diving in, you take a breath and start asking questions.

Transcript
You

"Before I start designing, I'd like to clarify some requirements. First, what's our scale? How many orders per day are we handling?"

Interviewer

"We're one of the larger players. About 5 million orders per day, concentrated heavily during lunch and dinner peaks."

You

"So during peak hours — say 6-8 PM — we might see 3-4x the average rate. What's the geographic scope? Single country or global?"

Interviewer

"Focus on a single country for now, but the design should be extensible to multiple regions."

You

"For the order flow, once a customer places an order, what's the expected time until a driver is assigned?"

Interviewer

"We want driver assignment within 30 seconds of restaurant confirmation. The restaurant should see the order within 2 seconds of placement."

You

"What about payment? Do we handle payments ourselves or use a payment processor?"

Interviewer

"We use Stripe for payment processing, but we need to handle the complexity of: authorization at order time, capture when driver picks up, and potential refunds."

You

"Last question — how important is real-time tracking? Do customers need live driver location?"

Interviewer

"Yes, live tracking is essential. Customers expect to see driver location updating every few seconds."

You

"Should the tracking screen poll for updates, or do we push them? And when the app is closed, how do customers hear that the order has moved on?"

Interviewer

"Push them while the app is open; polling every few seconds at dinner is a lot of empty answers. When it's closed, a push notification. Use the notification platform you'd design for this company anyway."

You

"Perfect. Let me summarize the requirements."

Functional Requirements#

Functional requirements
  1. ORDER PLACEMENT
    • Customer browses restaurants (filtered by location, cuisine, rating)
    • Customer adds items to cart and places order
    • Order is validated (restaurant open, items available, address deliverable)
    • Payment is authorized
  2. ORDER PROCESSING
    • Order sent to restaurant in real-time
    • Restaurant confirms and provides prep time estimate
    • System assigns optimal driver
    • Driver accepts/rejects assignment
  3. ORDER FULFILLMENT
    • Track order status: placed → confirmed → preparing → ready → picked up → delivered
    • Live tracking pushed over a persistent connection (SSE or WebSocket) while the app is open: status changes and driver location
    • ETA updates throughout
  4. NOTIFICATIONS (through the Week 6 platform: three lanes and an in-app inbox)
    • Critical: sign-in OTPs, new-device login alerts; driver assignment offers, which expire in seconds
    • Transactional: customer order updates (confirmed, driver assigned, driver arriving, delivered), restaurant new orders, refunds
    • Bulk: promotions and offers, paced and never ahead of an order update
    • Driver online status shown to dispatch and restaurants as presence
  5. PAYMENTS
    • Authorize at order placement
    • Capture when driver picks up
    • Handle refunds for cancellations or issues

Non-Functional Requirements#

Non-functional requirements
  1. SCALE
    • 5M orders/day → ~60 orders/sec average
    • Peak: 200-300 orders/sec during dinner rush
    • 100K concurrent users browsing
    • 50K active drivers at typical load; the dinner peak needs roughly 120-180K (200-300 orders/sec, each tying up a driver for ~20 minutes, ~2 orders per trip: 200-300 × 1,200 s ÷ 2)
    • ~400K active orders at the dinner peak, each one a live tracking connection while its customer has the app open
  2. LATENCY
    • Restaurant sees order: < 2 seconds
    • Driver assignment: < 30 seconds from restaurant confirmation
    • Location updates: every 3-5 seconds
    • Live tracking: a status change on the customer's screen in about a second
    • Order status API: < 100ms p99
  3. RELIABILITY
    • Orders must never be lost (durability)
    • Payments must be exactly-once (no double charges)
    • Driver assignment must be atomic (no double assignments)
  4. AVAILABILITY
    • 99.9% uptime (about 8.8 hours downtime/year max)
    • Graceful degradation during partial failures
Quick check2 questions · +5 XP each

Requirements clarification

  1. Q1/2 The interviewer says payment is authorized at order time but captured only when the driver picks up. Why split it this way?

    Answer & explanation

    Answer: C

    Authorization reserves the money; until capture nothing has moved, so failures before pickup are undone by voiding the authorization. Capture is tied to pickup because that is the point after which the order can no longer be cleanly undone.

  2. Q2/2 Which pair of reliability requirements maps most directly to idempotency and to atomic coordination?

    Answer & explanation

    Answer: A

    "Payments exactly-once" needs idempotency keys; "driver assignment must be atomic" needs locking so two orders cannot claim the same driver. The latency targets shape the design too, but they are performance requirements, not correctness guarantees.

Phase 2 · Stage 2 of 6 · 3 min

Phase 2: Back-of-Envelope Estimation#

You

"Let me work through the numbers to understand what we're building."

Traffic Estimation#

WorksheetOrder traffic
  • Daily orders: 5,000,000
  • Average rate: 5M / 86,400 = ~58 orders/sec
  • Peak multiplier: 4x during dinner (6-8 PM)
  • Peak rate: ~230 orders/sec
  • Per order, we have:
    • 1 order creation
    • 3-5 status updates
    • 10-20 location updates (30 min delivery, every 3 sec = 600 updates, but batched to 10-20 meaningful ones)
    • 5-10 notifications
  • Total writes at peak: 230 × 30 = ~7,000 writes/sec
  • Read traffic, browsing: 100K concurrent users, each browsing = 10 requests/min → 100K × 10 / 60 = ~17,000 reads/sec
  • Active orders: ≈ 100K on average (58/sec × ~30 min in flight), ~400K at the dinner peak (230/sec × 1,800 s ≈ 414,000)
  • Order tracking, if polled every 10 sec: ~40,000 reads/sec at peak, almost all of them "nothing new". Instead each tracking screen holds one live connection, so tracking is sized by ~400K connections, not a read rate
  • HTTP reads at peak: browsing ≈ ~17,000 reads/sec, plus order-status API calls
WorksheetThe real-time tier at the dinner peak (inputs assumed unless marked)
Value
Customer tracking connections: one per active order, sized as if every customer has the app open ~400K
Driver app connections (the dinner-peak fleet above) ~120-180K
Restaurant tablets (assumed) tens of thousands
Total live connections ~600K
Gateway nodes at 200K connections each (Week 6, Day 3's blast-radius cap) 3, × 1.5 to survive losing one of three zones → ~5 nodes
Heartbeats every 30 s 600K ÷ 30 ≈ 20,000/sec
Location frames to customers: the latest point every ~4 s per order 400K ÷ 4 ≈ 100,000/sec, coalesced per order
Order status changes (~4 per order × 230 orders/sec) ~1,000/sec, each with a sequence number
One zone of three lost ~200K sockets reconnect; at ~1,000 admissions/sec per surviving node, about a minute with jitter

Conclusion: the tier is small next to Week 6's 10M sockets, but it's sized the same way: by connections and how fast they can come back, not by messages. Location is replaceable, so a slow phone gets only the newest point; status changes are not, so they carry sequence numbers and a reconnecting app resumes after the last one it saw.

Storage Estimation#

WorksheetOrder data
  • Per order:
    • Order record: 2 KB (items, addresses, metadata)
    • Status history: 500 bytes (10 status changes × 50 bytes)
    • Location history: 2 KB (20 points × 100 bytes)
    • Total per order: ~5 KB
  • Daily storage: 5M × 5 KB = 25 GB/day
  • Yearly storage: 25 GB × 365 = ~9 TB/year
  • With 3x replication: ~27 TB/year
  • Active data (hot):
    • Last 24 hours orders: 5M × 5 KB = 25 GB
    • Active orders (in-flight): 100K × 5 KB = 500 MB
    • Driver locations: 50K × 100 bytes = 5 MB

Infrastructure Estimation#

Estimation summary

Traffic

  • Peak orders: 230/sec
  • Peak writes: 7,000/sec
  • Peak HTTP reads: ~17,000/sec
  • Live connections: ~600K

Storage

  • Daily new data: 25 GB
  • Hot data (24h): 25 GB
  • Yearly growth: 9 TB

Infrastructure (rough)

  • API servers: 20-30 instances
  • Database: Primary + 3 read replicas
  • Cache (Redis): 32 GB cluster
  • Message queue: Kafka with 32 partitions
  • Real-time gateways: ~5 nodes
Quick check3 questions · +5 XP each

Estimation

  1. Q1/3 5M orders/day with a 4x dinner-peak multiplier gives roughly what peak order rate?

    Answer & explanation

    Answer: D

    5M / 86,400 is about 58 orders/sec on average; 4x that is about 230/sec at the dinner peak. 60/sec is the average, which would under-provision for the rush; 2,300/sec is the 10x growth target discussed later.

  2. Q2/3 ~400K orders are being tracked at the dinner peak. Polling every 10 seconds would be ~40K reads/sec. What does the live-connection design size the tracking tier by instead?

    Answer & explanation

    Answer: C

    With one connection per tracking screen, the cost is holding sockets and re-admitting them after a failure, so the fleet is sized by connections. Messages are the small part: status changes are ~1,000/sec, and location frames are coalesced to the newest point per order.

  3. Q3/3 Each order stores about 5 KB in total. Roughly how much new order data accumulates per year, before replication?

    Answer & explanation

    Answer: B

    5M x 5 KB = 25 GB/day, and 25 GB x 365 is about 9 TB/year. 27 TB is the same figure after 3x replication; 25 GB is just one day.

Phase 3 · Stage 3 of 6 · 2 min

Phase 3: High-Level Design#

You

"Now let me sketch out the high-level architecture."

System Architecture#

DiagramHigh-level architecture
%% title: High-level architecture
flowchart TD
  subgraph Apps["Client apps"]
    direction TB
    CA@{ icon: "sd:phone", label: "Customer App" }
    RA@{ icon: "sd:store", label: "Restaurant App" }
    DA@{ icon: "sd:delivery", label: "Driver App" }
  end
  Apps --> CDN@{ icon: "sd:cdn", label: "CDN" }
  CDN --> ALB@{ icon: "tech:elb", label: "ALB" }
  ALB --> RL["Rate Limiter"]:::edge
  ALB --> OAPI["Order API"]:::core
  ALB --> DAPI["Driver API"]:::core
  ALB --> RAPI["Restaurant API"]:::core
  OAPI --> K@{ icon: "tech:kafka", label: "Kafka (Events)" }
  DAPI --> K
  RAPI --> K
  subgraph Workers["Event workers"]
    OW["Order Worker"]:::service
    DM["Driver Match"]:::service
    PW["Payment Worker"]:::service
    NW["Notif. Worker"]:::service
    TW["Track Worker"]:::service
  end
  K --> OW & DM & PW & NW & TW
  subgraph Storage["Storage"]
    direction TB
    PG@{ icon: "tech:postgresql", label: "PostgreSQL (Orders)" }
    RC@{ icon: "tech:redis", label: "Redis (Cache)" }
    S3@{ icon: "tech:s3", label: "S3/Blob (History)" }
  end
  Workers --> Storage
  NW --> NP["Notification platform<br/>critical · transactional · bulk lanes, inbox"]:::core
  NP --> PUSH@{ icon: "sd:bell", label: "APNs, FCM, SMS<br/>when no app is open" }
  NP --> GW@{ icon: "sd:gateway", label: "Real-time gateways<br/>WebSocket and SSE, ~600K sockets" }
  TW -->|"latest point per order"| GW
  GW -.->|"who holds which app"| REG@{ icon: "tech:redis", label: "Connection registry<br/>and presence" }
  GW ==>|"live tracking, new orders, offers"| Apps
  class CA,RA,DA client
  class CDN,ALB,GW edge
  class K queue
  class PG,S3 store
  class RC,REG cache
  class PUSH external

Component Breakdown#

You

"Let me walk through each component."

1. API Layer#

Order API: Handles order placement, status queries, cancellations

  • Validates orders (restaurant hours, delivery address, payment)
  • Writes to transactional outbox (Week 3 pattern)
  • Returns order ID immediately

Driver API: Handles driver location updates, assignment acceptance

  • High-frequency location updates (every 3-5 seconds)
  • Assignment accept/reject
  • Status updates (picked up, delivered)

Restaurant API: Order receipt, confirmation, prep time updates

  • New orders pushed down the restaurant tablet's WebSocket, held by the real-time gateways; the connection registry says which gateway holds it
  • Prep time estimates
  • Order ready signal

2. Event-Driven Core (Kafka)#

Topics:

  • orders.created - New orders for processing
  • orders.status - Status change events
  • driver.location - Location updates
  • driver.assignment - Assignment events
  • payments.process - Payment commands
  • notifications.critical, notifications.transactional, notifications.bulk - the notification platform's three lanes (Week 6, Day 2)

3. Workers#

Order Worker: Orchestrates order lifecycle via saga pattern (Week 5) Driver Matcher: Assigns optimal driver based on location, rating, load Payment Worker: Handles Stripe integration with idempotency (Week 2) Notification Worker: Turns order events into notifications on the right lane (Week 6) Tracking Worker: Processes location updates, calculates ETAs, and publishes each order's latest point to the gateway holding its customer

4. Real-Time Tier (Week 6)#

Gateways: Hold the customer, driver and restaurant apps' persistent connections (~600K at the dinner peak): SSE for the customer's tracking screen on the web, a WebSocket in the apps Connection registry: user or restaurant → gateway and epoch, so a status change or a new order reaches the one gateway that holds that app Presence: drivers' online status as soft state, from heartbeats with an expiry

Quick check2 questions · +5 XP each

High-level design

  1. Q1/2 The Order API writes to an outbox and returns the order ID immediately instead of calling the restaurant, driver and payment services in sequence. What does this buy?

    Answer & explanation

    Answer: B

    Once the order and its event are committed, workers for payments, matching and notifications consume independently, so one slow or failing worker does not block placement. The workers run in their own transactions; the outbox only ties the order row to its event.

  2. Q2/2 The Payment Worker crashes for a few minutes. With the event-driven design, what happens to the Notification and Tracking workers?

    Answer & explanation

    Answer: D

    Each worker consumes independently, so a failure in one is isolated and its backlog is processed on recovery. Kafka retains the events, which is why nothing is lost while the payment worker is down.

Phase 4 · Stage 4 of 6 · 28 min

Phase 4: Deep Dives#

Interviewer

"Great overview. Let's dive deeper. How do you ensure an order is never lost?"

Deep Dive 1: Reliable Order Processing (Week 3 - Transactional Outbox)#

You

"This is critical. We use the transactional outbox pattern to ensure orders are never lost, even if Kafka is temporarily unavailable."

The Problem#

Without outbox pattern

Customer places order:

  1. Write to database — Success
  2. Publish to Kafka — Failed: Kafka down!

Result: Order in DB but never processed. Customer's card is held for an order that never starts (no food delivered).

This is unacceptable for a food delivery system.

The Solution#

pythonOrder · 185 lines
# services/order_service.py
# Applies: Week 3, Day 2 - Transactional Outbox

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Optional
import uuid
import json


@dataclass
class Order:
    order_id: str
    customer_id: str
    restaurant_id: str
    items: list
    delivery_address: dict
    total_amount: float
    status: str
    created_at: datetime


@dataclass
class OutboxMessage:
    message_id: str
    aggregate_type: str
    aggregate_id: str
    event_type: str
    payload: dict
    created_at: datetime


class OrderService:
    """
    Order service with transactional outbox pattern.
    
    Guarantees:
    - Order and outbox message written atomically
    - Events eventually published to Kafka
    - No order is ever lost
    """
    
    def __init__(self, db_pool, payment_service):
        self.db = db_pool
        self.payments = payment_service
    
    async def place_order(self, request: dict) -> Order:
        """
        Place an order with guaranteed event publishing.
        
        Uses single transaction for order + outbox.
        """
        
        # The client creates request_id once per checkout and resends it on
        # retries and double-clicks, so every attempt maps to the same
        # order_id and the same authorization key (a server-side uuid4
        # would make each click a new order and a second authorization)
        order_id = str(uuid.uuid5(
            uuid.NAMESPACE_URL,
            f"order:{request['customer_id']}:{request['request_id']}"
        ))
        
        # Authorize payment first (can fail, no order created yet)
        auth_result = await self.payments.authorize(
            customer_id=request["customer_id"],
            amount=request["total_amount"],
            idempotency_key=f"order:{order_id}:auth"
        )
        
        if not auth_result.success:
            raise PaymentAuthorizationError(auth_result.error)
        
        # Single transaction: order + outbox
        async with self.db.transaction() as tx:
            # Insert order
            order = Order(
                order_id=order_id,
                customer_id=request["customer_id"],
                restaurant_id=request["restaurant_id"],
                items=request["items"],
                delivery_address=request["delivery_address"],
                total_amount=request["total_amount"],
                status="placed",
                created_at=datetime.now(timezone.utc)
            )
            
            inserted = await tx.fetchval("""
                INSERT INTO orders (
                    order_id, customer_id, restaurant_id, items,
                    delivery_address, total_amount, status, created_at,
                    payment_auth_id
                ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
                ON CONFLICT (order_id) DO NOTHING
                RETURNING order_id
            """, order.order_id, order.customer_id, order.restaurant_id,
                json.dumps(order.items), json.dumps(order.delivery_address),
                order.total_amount, order.status, order.created_at,
                auth_result.authorization_id)
            
            if inserted is None:
                # A retry of an order already placed: same order_id, and
                # no second OrderPlaced event
                return order
            
            # Insert outbox message (same transaction!)
            outbox_message = OutboxMessage(
                message_id=str(uuid.uuid4()),
                aggregate_type="Order",
                aggregate_id=order_id,
                event_type="OrderPlaced",
                payload={
                    "order_id": order_id,
                    "customer_id": order.customer_id,
                    "restaurant_id": order.restaurant_id,
                    "items": order.items,
                    "total_amount": order.total_amount,
                    "payment_auth_id": auth_result.authorization_id,
                },
                created_at=datetime.now(timezone.utc)
            )
            
            await tx.execute("""
                INSERT INTO outbox (
                    message_id, aggregate_type, aggregate_id,
                    event_type, payload, created_at
                ) VALUES ($1, $2, $3, $4, $5, $6)
            """, outbox_message.message_id, outbox_message.aggregate_type,
                outbox_message.aggregate_id, outbox_message.event_type,
                json.dumps(outbox_message.payload), outbox_message.created_at)
        
        # Transaction committed - order is durable
        # Outbox publisher will send to Kafka eventually
        
        return order


class OutboxPublisher:
    """
    Publishes outbox messages to Kafka.
    
    Runs as background process, polling for unpublished messages.
    """
    
    def __init__(self, db_pool, kafka_producer):
        self.db = db_pool
        self.kafka = kafka_producer
    
    async def poll_and_publish(self, batch_size: int = 100):
        """Poll outbox and publish to Kafka."""
        
        async with self.db.transaction() as tx:
            # Lock and fetch unpublished messages
            messages = await tx.fetch("""
                SELECT * FROM outbox
                WHERE published_at IS NULL
                ORDER BY created_at
                LIMIT $1
                FOR UPDATE SKIP LOCKED
            """, batch_size)
            
            for msg in messages:
                topic = self._get_topic(msg["event_type"])
                
                # Publish to Kafka
                await self.kafka.send(
                    topic=topic,
                    key=msg["aggregate_id"].encode(),
                    value=json.dumps(msg["payload"]).encode(),
                    headers=[("message_id", msg["message_id"].encode())]
                )
                
                # Mark as published
                await tx.execute("""
                    UPDATE outbox 
                    SET published_at = NOW()
                    WHERE message_id = $1
                """, msg["message_id"])
    
    def _get_topic(self, event_type: str) -> str:
        return {
            "OrderPlaced": "orders.created",
            "OrderConfirmed": "orders.status",
            "OrderReady": "orders.status",
            "OrderDelivered": "orders.status",
        }.get(event_type, "orders.events")
Transcript
Interviewer

"What happens if the outbox publisher crashes?"

You

"Since we mark messages as published only after Kafka confirms receipt, a crash means the message stays in the outbox. Next poll picks it up. We might publish twice, which is why downstream consumers must be idempotent."

Deep Dive 2: Payment Processing (Week 2 - Idempotency)#

Transcript
Interviewer

"Speaking of payments — how do you ensure we never double-charge a customer?"

You

"This is where idempotency is critical. We use idempotency keys for every payment operation."

The Problem#

Double charge scenario
  1. Customer places order
  2. We call Stripe to authorize (create a PaymentIntent)
  3. Network timeout — did it succeed?
  4. We retry the call
  5. Without an idempotency key, Stripe creates a second PaymentIntent: customer gets two authorization holds (two PaymentIntents), which become a double charge if both are captured!

(Capture is safer by nature, since Stripe rejects a second capture of the same PaymentIntent, but a retry still needs a stable key to get back the original result instead of an error.)

This WILL happen at scale. We need to prevent it.

The Solution#

pythonPaymentStatus · 191 lines
# services/payment_service.py
# Applies: Week 2, Day 2 - Idempotency

from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional
from enum import Enum
import hashlib


class PaymentStatus(Enum):
    PENDING = "pending"
    AUTHORIZED = "authorized"
    CAPTURED = "captured"
    REFUNDED = "refunded"
    FAILED = "failed"


@dataclass 
class PaymentRecord:
    payment_id: str
    order_id: str
    amount: float
    status: PaymentStatus
    idempotency_key: str
    stripe_payment_intent_id: Optional[str]
    created_at: datetime
    updated_at: datetime


class PaymentService:
    """
    Payment service with idempotency guarantees.
    
    Every operation uses idempotency keys to ensure:
    - Authorization happens exactly once
    - Capture happens exactly once
    - Refund happens exactly once
    """
    
    def __init__(self, db_pool, stripe_client, redis_client):
        self.db = db_pool
        self.stripe = stripe_client
        self.redis = redis_client
        
        # Idempotency window (Week 2 concept)
        self.idempotency_ttl = timedelta(hours=24)
    
    async def authorize(
        self,
        customer_id: str,
        amount: float,
        idempotency_key: str
    ) -> AuthorizationResult:
        """
        Authorize payment with idempotency.
        
        Same idempotency_key always returns same result.
        """
        
        # Check if we've seen this key before
        existing = await self._get_idempotent_result(idempotency_key)
        if existing:
            return existing
        
        # Try to acquire lock for this key
        lock_acquired = await self._acquire_idempotency_lock(idempotency_key)
        if not lock_acquired:
            # Another request is processing - wait and return their result
            return await self._wait_for_result(idempotency_key)
        
        try:
            # Create payment intent with Stripe
            # Stripe also accepts idempotency key!
            intent = await self.stripe.create_payment_intent(
                amount=int(amount * 100),  # cents
                currency="usd",
                customer=customer_id,
                capture_method="manual",  # Authorize only
                idempotency_key=idempotency_key
            )
            
            result = AuthorizationResult(
                success=True,
                authorization_id=intent.id,
                amount=amount
            )
            
            # Store result for future idempotent requests
            await self._store_idempotent_result(idempotency_key, result)
            
            return result
            
        except StripeError as e:
            result = AuthorizationResult(
                success=False,
                error=str(e)
            )
            # Only cache deterministic outcomes (e.g. card declined). Transient
            # errors (network, 5xx, rate limit) must stay retryable, or every
            # retry with this key would replay the failure for 24 hours
            if isinstance(e, CardError):
                await self._store_idempotent_result(idempotency_key, result)
            return result
            
        finally:
            await self._release_idempotency_lock(idempotency_key)
    
    async def capture(
        self,
        order_id: str,
        authorization_id: str,
        amount: float
    ) -> CaptureResult:
        """
        Capture authorized payment.
        
        Idempotency key derived from order_id ensures exactly-once capture.
        """
        
        idempotency_key = f"capture:{order_id}"
        
        existing = await self._get_idempotent_result(idempotency_key)
        if existing:
            return existing
        
        lock_acquired = await self._acquire_idempotency_lock(idempotency_key)
        if not lock_acquired:
            return await self._wait_for_result(idempotency_key)
        
        try:
            # Check current state in our database
            payment = await self._get_payment_by_order(order_id)
            
            if payment and payment.status == PaymentStatus.CAPTURED:
                # Already captured - return success
                return CaptureResult(success=True, captured=True)
            
            # Capture with Stripe (Stripe idempotency handles their side)
            intent = await self.stripe.capture_payment_intent(
                authorization_id,
                amount_to_capture=int(amount * 100),
                idempotency_key=idempotency_key
            )
            
            # Update our database
            await self._update_payment_status(
                order_id,
                PaymentStatus.CAPTURED
            )
            
            result = CaptureResult(success=True, captured=True)
            await self._store_idempotent_result(idempotency_key, result)
            
            return result
            
        except StripeError as e:
            result = CaptureResult(success=False, error=str(e))
            if isinstance(e, CardError):  # Cache deterministic failures only
                await self._store_idempotent_result(idempotency_key, result)
            return result
            
        finally:
            await self._release_idempotency_lock(idempotency_key)
    
    async def _acquire_idempotency_lock(self, key: str) -> bool:
        """Acquire distributed lock for idempotency key."""
        lock_key = f"idem_lock:{key}"
        return await self.redis.set(
            lock_key,
            "1",
            nx=True,  # Only if not exists
            ex=30     # 30 second timeout
        )
    
    async def _get_idempotent_result(self, key: str):
        """Get stored result for idempotency key."""
        result_key = f"idem_result:{key}"
        data = await self.redis.get(result_key)
        if data:
            return self._deserialize_result(data)
        return None
    
    async def _store_idempotent_result(self, key: str, result):
        """Store result for future idempotent requests."""
        result_key = f"idem_result:{key}"
        await self.redis.setex(
            result_key,
            int(self.idempotency_ttl.total_seconds()),
            self._serialize_result(result)
        )
Quick check3 questions · +5 XP each

Outbox and payment idempotency

  1. Q1/3 The outbox publisher sends a message to Kafka, then crashes before its transaction commits published_at. What follows?

    Answer & explanation

    Answer: A

    The outbox row is still unpublished after the rollback, so it is re-sent; the design accepts duplicates and relies on idempotent consumers. The order row was committed earlier and is safe; this is a duplicate-event problem, not a lost-order one.

  2. Q2/3 A capture call to Stripe times out and the worker retries. What prevents a double charge?

    Answer & explanation

    Answer: C

    Deriving the key from the order ID means every retry for that capture carries the same key, and Stripe treats it as the same operation. A fresh key per attempt would look like a new capture to Stripe, which is exactly the double charge.

  3. Q3/3 Two requests to capture the same order arrive at the same moment. What does the Redis SET NX lock add?

    Answer & explanation

    Answer: B

    Without the lock, both could miss the stored result and both call Stripe; the lock serializes them and the loser reads the winner's result. Stripe's own idempotency key is still passed as a second line of defence.

Deep Dive 3: Driver Assignment (Week 5 - Distributed Coordination)#

Transcript
Interviewer

"How do you assign drivers without double-assigning the same driver to multiple orders?"

You

"This requires distributed coordination. We can't have two orders grab the same driver simultaneously."

The Problem#

Double assignment race condition
Step Order A needs driver Order B needs driver
1 Query available drivers Query available drivers
2 See Driver X is free See Driver X is free
3 Assign Driver X to A Assign Driver X to B

4. Driver X has two orders!

At 200 orders/sec during peak, this WILL happen.

The Solution#

pythonDriver · 193 lines
# services/driver_matcher.py
# Applies: Week 5, Day 5 - Leader Election & Coordination

from dataclasses import dataclass
from datetime import datetime
from typing import Optional, List
import asyncio
import math


@dataclass
class Driver:
    driver_id: str
    location: tuple  # (lat, lng)
    status: str      # available, assigned, busy
    rating: float
    current_order_id: Optional[str]


@dataclass
class AssignmentResult:
    success: bool
    driver_id: Optional[str] = None
    error: Optional[str] = None


class DriverMatcher:
    """
    Assigns drivers to orders with coordination guarantees.
    
    Uses a row lock (SELECT ... FOR UPDATE) plus a version check
    to prevent double assignment.
    """
    
    def __init__(self, db_pool, redis_client, location_service):
        self.db = db_pool
        self.redis = redis_client
        self.locations = location_service
    
    async def assign_driver(
        self,
        order_id: str,
        restaurant_location: tuple,
        max_distance_km: float = 5.0
    ) -> AssignmentResult:
        """
        Find and assign optimal driver for an order.
        
        Uses row locking + version check to prevent race conditions.
        """
        
        # Find candidate drivers near restaurant
        candidates = await self._find_nearby_drivers(
            restaurant_location,
            max_distance_km
        )
        
        if not candidates:
            return AssignmentResult(
                success=False,
                error="No available drivers nearby"
            )
        
        # Sort by score (distance, rating, etc.)
        ranked = self._rank_drivers(candidates, restaurant_location)
        
        # Try to assign, starting with best candidate
        for driver in ranked:
            result = await self._try_assign(order_id, driver)
            if result.success:
                return result
        
        return AssignmentResult(
            success=False,
            error="All nearby drivers unavailable"
        )
    
    async def _try_assign(
        self,
        order_id: str,
        driver: Driver
    ) -> AssignmentResult:
        """
        Attempt to assign driver (row lock + version check).
        
        Only succeeds if driver is still available.
        """
        
        async with self.db.transaction() as tx:
            # Lock the driver row and check status
            current = await tx.fetchrow("""
                SELECT driver_id, status, version
                FROM drivers
                WHERE driver_id = $1
                FOR UPDATE
            """, driver.driver_id)
            
            if not current or current["status"] != "available":
                # Driver no longer available
                return AssignmentResult(success=False)
            
            # Update driver status atomically
            result = await tx.execute("""
                UPDATE drivers
                SET status = 'assigned',
                    current_order_id = $1,
                    version = version + 1,
                    assigned_at = NOW()
                WHERE driver_id = $2
                  AND version = $3
                  AND status = 'available'
            """, order_id, driver.driver_id, current["version"])
            
            if result == "UPDATE 0":
                # Concurrent modification - another order got them
                return AssignmentResult(success=False)
            
            # Create assignment record
            await tx.execute("""
                INSERT INTO assignments (
                    assignment_id, order_id, driver_id,
                    status, created_at
                ) VALUES (gen_random_uuid(), $1, $2, 'pending', NOW())
            """, order_id, driver.driver_id)
        
        # Successfully assigned!
        return AssignmentResult(
            success=True,
            driver_id=driver.driver_id
        )
    
    async def _find_nearby_drivers(
        self,
        location: tuple,
        max_distance_km: float
    ) -> List[Driver]:
        """Find available drivers within radius."""
        
        # Use Redis GEO for fast spatial query
        lat, lng = location
        
        # Get driver IDs within radius
        nearby_ids = await self.redis.georadius(
            "driver_locations",
            lng, lat,
            max_distance_km,
            unit="km",
            count=50
        )
        
        if not nearby_ids:
            return []
        
        # Fetch driver details (only available ones)
        drivers = await self.db.fetch("""
            SELECT driver_id, status, rating
            FROM drivers
            WHERE driver_id = ANY($1)
              AND status = 'available'
        """, [d.decode() for d in nearby_ids])
        
        return [
            Driver(
                driver_id=d["driver_id"],
                location=await self.locations.get(d["driver_id"]),
                status=d["status"],
                rating=d["rating"],
                current_order_id=None
            )
            for d in drivers
        ]
    
    def _rank_drivers(
        self,
        drivers: List[Driver],
        restaurant_location: tuple
    ) -> List[Driver]:
        """Rank drivers by assignment score."""
        
        def score(driver: Driver) -> float:
            distance = self._haversine(driver.location, restaurant_location)
            # Lower distance is better, higher rating is better
            return distance - (driver.rating * 0.5)
        
        return sorted(drivers, key=score)
    
    @staticmethod
    def _haversine(a: tuple, b: tuple) -> float:
        """Great-circle distance in km between two (lat, lng) points."""
        lat1, lng1, lat2, lng2 = map(math.radians, (*a, *b))
        h = (math.sin((lat2 - lat1) / 2) ** 2
             + math.cos(lat1) * math.cos(lat2) * math.sin((lng2 - lng1) / 2) ** 2)
        return 2 * 6371.0 * math.asin(math.sqrt(h))

Deep Dive 4: Order Saga (Week 5 - Saga Pattern)#

Transcript
Interviewer

"Walk me through what happens if something fails mid-order — say the restaurant rejects the order after payment is authorized?"

You

"This is a classic saga pattern problem. We need compensation logic for each step."

The Order Saga#

pythonSagaStep · 304 lines
# services/order_saga.py
# Applies: Week 5, Day 2-3 - Saga Pattern

from dataclasses import dataclass
from enum import Enum
from typing import Optional
import asyncio
import logging

logger = logging.getLogger(__name__)


class SagaStep(Enum):
    VALIDATE_ORDER = "validate"
    AUTHORIZE_PAYMENT = "authorize_payment"
    NOTIFY_RESTAURANT = "notify_restaurant"
    AWAIT_CONFIRMATION = "await_confirmation"
    ASSIGN_DRIVER = "assign_driver"
    CAPTURE_PAYMENT = "capture_payment"
    COMPLETE = "complete"


class SagaStatus(Enum):
    RUNNING = "running"
    COMPLETED = "completed"
    COMPENSATING = "compensating"
    FAILED = "failed"


@dataclass
class OrderSagaState:
    saga_id: str
    order_id: str
    current_step: SagaStep
    status: SagaStatus
    payment_auth_id: Optional[str] = None
    driver_id: Optional[str] = None
    compensation_reason: Optional[str] = None


class OrderSagaOrchestrator:
    """
    Orchestrates order lifecycle as a saga.
    
    Each step has a corresponding compensation action.
    If any step fails, we run compensations in reverse order.
    """
    
    def __init__(
        self,
        order_service,
        payment_service,
        restaurant_service,
        driver_service,
        notification_service,
        saga_repo
    ):
        self.orders = order_service
        self.payments = payment_service
        self.restaurants = restaurant_service
        self.drivers = driver_service
        self.notifications = notification_service
        self.sagas = saga_repo
    
    async def execute(self, order_id: str) -> OrderSagaState:
        """Execute order saga from start to completion."""
        
        state = OrderSagaState(
            saga_id=f"saga:{order_id}",
            order_id=order_id,
            current_step=SagaStep.VALIDATE_ORDER,
            status=SagaStatus.RUNNING
        )
        
        await self.sagas.save(state)
        
        try:
            # Step 1: Validate order
            state = await self._validate_order(state)
            
            # Step 2: Authorize payment
            state = await self._authorize_payment(state)
            
            # Step 3: Notify restaurant
            state = await self._notify_restaurant(state)
            
            # Step 4: Wait for restaurant confirmation
            # (This happens async via webhook/event)
            state.current_step = SagaStep.AWAIT_CONFIRMATION
            await self.sagas.save(state)
            
            return state
            
        except SagaStepError as e:
            logger.error(f"Saga {state.saga_id} failed at {state.current_step}: {e}")
            return await self._compensate(state, str(e))
    
    async def on_restaurant_confirmed(self, order_id: str, prep_time_minutes: int):
        """Called when restaurant confirms order."""
        
        state = await self.sagas.get_by_order(order_id)
        if not state or state.status != SagaStatus.RUNNING:
            return
        
        try:
            # Step 5: Assign driver
            state = await self._assign_driver(state)
            
            # Update order with driver info
            await self.orders.update_status(
                order_id,
                "driver_assigned",
                driver_id=state.driver_id
            )
            
            # Notify customer
            await self.notifications.send(
                user_id=state.customer_id,
                type="driver_assigned",
                template="driver_assigned",
                variables={"driver_id": state.driver_id}
            )
            
            state.current_step = SagaStep.COMPLETE
            state.status = SagaStatus.COMPLETED
            await self.sagas.save(state)
            
        except SagaStepError as e:
            await self._compensate(state, str(e))
    
    async def on_restaurant_rejected(self, order_id: str, reason: str):
        """Called when restaurant rejects order."""
        
        state = await self.sagas.get_by_order(order_id)
        if not state:
            return
        
        await self._compensate(state, f"Restaurant rejected: {reason}")
    
    async def on_driver_picked_up(self, order_id: str):
        """Called when driver picks up order — capture payment."""
        
        state = await self.sagas.get_by_order(order_id)
        if not state or not state.payment_auth_id:
            return
        
        # Capture payment (idempotent)
        result = await self.payments.capture(
            order_id=order_id,
            authorization_id=state.payment_auth_id,
            amount=state.order_amount
        )
        
        if not result.success:
            logger.error(f"Payment capture failed for {order_id}: {result.error}")
            # Don't compensate here — food is already with driver
            # Flag for manual review instead
    
    async def _compensate(
        self,
        state: OrderSagaState,
        reason: str
    ) -> OrderSagaState:
        """
        Run compensation actions in reverse order.
        
        Compensation order:
        1. Release driver (if assigned)
        2. Void payment authorization (if authorized)
        3. Update order status to cancelled
        4. Notify customer
        """
        
        state.status = SagaStatus.COMPENSATING
        state.compensation_reason = reason
        await self.sagas.save(state)
        
        logger.info(f"Starting compensation for saga {state.saga_id}")
        
        # Compensate driver assignment
        if state.driver_id:
            await self._retry_until_done(
                "release driver",
                lambda: self.drivers.release(state.driver_id)
            )
            logger.info(f"Released driver {state.driver_id}")
        
        # Compensate payment authorization
        if state.payment_auth_id:
            await self._retry_until_done(
                "void payment",
                lambda: self.payments.void_authorization(
                    authorization_id=state.payment_auth_id,
                    idempotency_key=f"void:{state.order_id}"
                )
            )
            logger.info(f"Voided payment auth {state.payment_auth_id}")
        
        # Update order status
        await self.orders.update_status(
            state.order_id,
            "cancelled",
            reason=reason
        )
        
        # Notify customer
        await self.notifications.send(
            user_id=state.customer_id,
            type="order_cancelled",
            template="order_cancelled",
            variables={"reason": reason}
        )
        
        state.status = SagaStatus.FAILED
        await self.sagas.save(state)
        
        return state
    
    async def _retry_until_done(self, name: str, action) -> None:
        """
        Compensations must eventually succeed (Week 5), so a failure is
        retried with capped backoff, never just logged. Both actions are
        idempotent, so retrying is safe; the saga stays COMPENSATING in
        the repo meanwhile, so a restarted orchestrator can resume it.
        """
        delay = 1
        while True:
            try:
                await action()
                return
            except Exception as e:
                logger.error(f"Failed to {name}, retrying in {delay}s: {e}")
                await asyncio.sleep(delay)
                delay = min(delay * 2, 60)
    
    async def _validate_order(self, state: OrderSagaState) -> OrderSagaState:
        """Validate order details."""
        order = await self.orders.get(state.order_id)
        
        # Check restaurant is open
        restaurant = await self.restaurants.get(order.restaurant_id)
        if not restaurant.is_open():
            raise SagaStepError("Restaurant is closed")
        
        # Check delivery address is in range
        if not restaurant.delivers_to(order.delivery_address):
            raise SagaStepError("Delivery address out of range")
        
        state.current_step = SagaStep.AUTHORIZE_PAYMENT
        state.customer_id = order.customer_id
        state.order_amount = order.total_amount
        await self.sagas.save(state)
        
        return state
    
    async def _authorize_payment(self, state: OrderSagaState) -> OrderSagaState:
        """Authorize payment."""
        result = await self.payments.authorize(
            customer_id=state.customer_id,
            amount=state.order_amount,
            idempotency_key=f"order:{state.order_id}:auth"
        )
        
        if not result.success:
            raise SagaStepError(f"Payment failed: {result.error}")
        
        state.payment_auth_id = result.authorization_id
        state.current_step = SagaStep.NOTIFY_RESTAURANT
        await self.sagas.save(state)
        
        return state
    
    async def _notify_restaurant(self, state: OrderSagaState) -> OrderSagaState:
        """Send order to restaurant."""
        order = await self.orders.get(state.order_id)
        
        await self.restaurants.send_order(
            restaurant_id=order.restaurant_id,
            order=order
        )
        
        state.current_step = SagaStep.AWAIT_CONFIRMATION
        await self.sagas.save(state)
        
        return state
    
    async def _assign_driver(self, state: OrderSagaState) -> OrderSagaState:
        """Assign driver to order."""
        order = await self.orders.get(state.order_id)
        restaurant = await self.restaurants.get(order.restaurant_id)
        
        result = await self.drivers.assign(
            order_id=state.order_id,
            restaurant_location=restaurant.location
        )
        
        if not result.success:
            raise SagaStepError(f"Driver assignment failed: {result.error}")
        
        state.driver_id = result.driver_id
        state.current_step = SagaStep.COMPLETE
        await self.sagas.save(state)
        
        return state
ExplainerOrder saga: authorize, compensate, capture

The saga validates the order, authorizes payment with key order:{id}:auth (a hold, nothing captured), sends the order to the restaurant (seen within 2 s), awaits confirmation and assigns a driver (within 30 s), then captures at pickup, idempotently. If the restaurant rejects after authorization, compensation runs in reverse: there is no driver to release, the authorization is voided with key void:{order_id} (retried until done), the order is cancelled and the customer notified. If capture fails after pickup, nothing is compensated, since pickup can't be undone; the failure is flagged for manual review.

Quick check3 questions · +5 XP each

Driver assignment and the order saga

  1. Q1/3 Orders A and B both pick Driver X as the best candidate at the same moment. What happens in _try_assign?

    Answer & explanation

    Answer: D

    FOR UPDATE serializes access to the driver row, and the status = 'available' and version checks make the second update a no-op. The loser is not stuck; the matcher loops through its ranked candidates until one assignment succeeds.

  2. Q2/3 The restaurant rejects an order after payment was authorized. What does the saga's compensation do with the payment?

    Answer & explanation

    Answer: B

    Nothing was captured yet, so the compensating action is to void the hold (with its own idempotency key), then mark the order cancelled and tell the customer. Capturing and refunding would move money needlessly and can show up as a charge on the customer's statement.

  3. Q3/3 Payment capture fails after the driver has picked up the food. Why doesn't the saga compensate?

    Answer & explanation

    Answer: A

    Compensation only makes sense for steps that can be reversed; once the food is with the driver, the safe move is to retry and escalate rather than cancel. Cancelling the order at this point would hurt the customer without getting the food back.

Deep Dive 5: Real-Time Location and Live Tracking (Weeks 4 and 6)#

Transcript
Interviewer

"With 50K active drivers updating location every 3 seconds, how do you handle that load? And how does the customer see it?"

You

"Two halves. Ingesting locations is a caching problem: Redis GEO for spatial queries, history written behind in batches. Showing them is a real-time delivery problem: instead of 400K tracking screens polling every 10 seconds, each holds one connection to our gateways and we push the latest point down it."

pythonLocationService · 125 lines
# services/location_service.py
# Applies: Week 4 - Caching, Week 1 - Hot Keys

from datetime import datetime, timedelta
from typing import Optional, List, Tuple
import asyncio


class LocationService:
    """
    High-frequency location tracking with caching.
    
    Scale: 50K drivers × 1 update/3sec = 17K updates/sec
    (~40-60K/sec with the 120-180K drivers of a dinner peak)
    
    Strategy:
    - Redis GEO for current locations (hot data)
    - Batch writes to PostgreSQL for history (cold data)
    - Customers don't read from here: TrackingPublisher (below) pushes to them
    """
    
    def __init__(self, redis_client, db_pool):
        self.redis = redis_client
        self.db = db_pool
        self._history_buffer = []
        self._buffer_lock = asyncio.Lock()
    
    async def update_location(
        self,
        driver_id: str,
        lat: float,
        lng: float,
        timestamp: datetime
    ):
        """
        Update driver location.
        
        Hot path: Redis GEO (immediate)
        Cold path: Batched PostgreSQL writes (async)
        """
        
        # Update Redis GEO (immediate, for spatial queries)
        await self.redis.geoadd(
            "driver_locations",
            lng, lat, driver_id
        )
        
        # Store current location with timestamp
        await self.redis.hset(
            f"driver:{driver_id}:location",
            mapping={
                "lat": str(lat),
                "lng": str(lng),
                "timestamp": timestamp.isoformat()
            }
        )
        
        # Buffer for batch history write
        async with self._buffer_lock:
            self._history_buffer.append({
                "driver_id": driver_id,
                "lat": lat,
                "lng": lng,
                "timestamp": timestamp
            })
            
            # Flush if buffer is large
            if len(self._history_buffer) >= 1000:
                await self._flush_history()
    
    async def get_driver_location(
        self,
        driver_id: str
    ) -> Optional[Tuple[float, float, datetime]]:
        """Get current location for a driver."""
        
        data = await self.redis.hgetall(f"driver:{driver_id}:location")
        
        if not data:
            return None
        
        return (
            float(data[b"lat"]),
            float(data[b"lng"]),
            datetime.fromisoformat(data[b"timestamp"].decode())
        )
    
    async def find_nearby_drivers(
        self,
        lat: float,
        lng: float,
        radius_km: float,
        limit: int = 50
    ) -> List[str]:
        """Find drivers within radius using Redis GEO."""
        
        results = await self.redis.georadius(
            "driver_locations",
            lng, lat,
            radius_km,
            unit="km",
            count=limit,
            sort="ASC"  # Closest first
        )
        
        return [r.decode() for r in results]
    
    async def _flush_history(self):
        """Batch write location history to PostgreSQL."""
        
        if not self._history_buffer:
            return
        
        buffer = self._history_buffer
        self._history_buffer = []
        
        # Batch insert
        await self.db.executemany("""
            INSERT INTO driver_location_history 
            (driver_id, lat, lng, recorded_at)
            VALUES ($1, $2, $3, $4)
        """, [
            (h["driver_id"], h["lat"], h["lng"], h["timestamp"])
            for h in buffer
        ])

Live Tracking Over the Real-Time Tier#

You

"The customer's tracking screen opens one connection: SSE on the web, since tracking is almost all server-to-client, or the app's existing WebSocket. Two kinds of event go down it, and they're treated differently."

Two kinds of tracking event

Driver location

  • Replaceable: only the newest point matters
  • Coalesced per order: a slow phone gets the latest point, never a backlog
  • No sequence number, no ack, no retry
  • Lost on a reconnect? The next point arrives in ~4 s

Order status

  • Durable: "picked up" must never be skipped
  • Written first, with the next sequence number for the order
  • Pushed as a hint; the app acks the sequence number
  • Lost on a reconnect? The app resumes after its last sequence number
pythonTrackingPublisher · 42 lines
# services/tracking_publisher.py
# Applies: Week 6, Day 3 - registry, one pub/sub channel per gateway, resume by sequence number

class TrackingPublisher:
    """
    Pushes tracking events to the gateway holding each customer's app.

    - Location is coalesced: the gateway keeps only the newest point per order
      in each connection's outbound queue (coalesce_key), so a slow client
      never builds a backlog.
    - Status changes are written to the order's event log with a sequence
      number before anyone is told; the socket message is only a hint.
    """

    def __init__(self, registry, pubsub, order_events):
        self.registry = registry          # user -> [(device, gateway_id, epoch)]
        self.pubsub = pubsub              # sharded pub/sub, one channel per gateway
        self.events = order_events        # durable, (order_id, seq) unique

    async def on_location(self, order, lat, lng, eta_minutes):
        frame = {
            "type": "location",
            "order_id": order.order_id,
            "lat": lat, "lng": lng, "eta_minutes": eta_minutes,
            "coalesce_key": f"loc:{order.order_id}",   # newest replaces any queued point
        }
        await self._publish(order.customer_id, frame)

    async def on_status(self, order, status):
        seq = await self.events.append(order.order_id, status)    # durable first
        frame = {"type": "status", "order_id": order.order_id,
                 "seq": seq, "status": status}
        await self._publish(order.customer_id, frame)
        return seq

    async def resume(self, order_id, after_seq):
        """A reconnecting app sends its last seq; it gets what it missed."""
        return await self.events.read_after(order_id, after_seq)

    async def _publish(self, user_id, frame):
        for conn in await self.registry.live_connections(user_id):  # skips dead leases
            await self.pubsub.spublish(f"gw:{conn.gateway_id}", {"user": user_id, **frame})
Transcript
Interviewer

"What happens when the customer's phone switches from Wi-Fi to 4G halfway through?"

You

"The TCP connection dies without the server being told. The app treats the network change as a reconnect trigger, reconnects with jittered backoff to any healthy gateway, and sends 'resume after seq 3'. It gets any status changes it missed from the order's log, then the next location point, about 4 seconds later. If a gateway crashes, the same thing happens for everyone on it: a publish to a dead gateway is lost, which is fine, because the socket was never the record."

Deep Dive 6: Rate Limiting & Backpressure (Week 1 & 3)#

Transcript
Interviewer

"What if someone tries to abuse the system — placing hundreds of orders?"

You

"We implement rate limiting at multiple layers and handle backpressure gracefully."

pythonMultiLayerRateLimiter · 143 lines
# services/rate_limiter.py
# Applies: Week 1, Day 3 - Rate Limiting; Week 3, Day 3 - Backpressure

from datetime import datetime, timezone
from typing import Tuple
import uuid


class MultiLayerRateLimiter:
    """
    Rate limiting at multiple layers.
    
    Layers:
    1. Per-user: 10 orders/hour
    2. Per-IP: 100 requests/minute  
    3. Per-restaurant: 500 orders/hour (prevent overwhelming kitchen)
    4. Global: System capacity protection
    """
    
    def __init__(self, redis_client):
        self.redis = redis_client
        
        self.limits = {
            "user_orders": {"count": 10, "window": 3600},      # 10/hour
            "ip_requests": {"count": 100, "window": 60},       # 100/min
            "restaurant_orders": {"count": 500, "window": 3600}, # 500/hour
            "global_orders": {"count": 1000, "window": 1},     # 1000/sec
        }
    
    async def check_order_allowed(
        self,
        user_id: str,
        ip_address: str,
        restaurant_id: str
    ) -> Tuple[bool, str]:
        """
        Check if order is allowed by all rate limits.
        
        Returns (allowed, reason).
        """
        
        # Check user limit
        if not await self._check_limit(f"ratelimit:user:{user_id}", "user_orders"):
            return (False, "Too many orders. Please try again later.")
        
        # Check IP limit
        if not await self._check_limit(f"ratelimit:ip:{ip_address}", "ip_requests"):
            return (False, "Too many requests. Please slow down.")
        
        # Check restaurant limit
        if not await self._check_limit(f"ratelimit:restaurant:{restaurant_id}", "restaurant_orders"):
            return (False, "Restaurant is very busy. Please try again soon.")
        
        # Check global limit
        if not await self._check_limit("ratelimit:global:orders", "global_orders"):
            return (False, "System is busy. Please try again in a moment.")
        
        return (True, "")
    
    # Atomic sliding window: trim, count, and record the attempt only if
    # it is under the limit
    SLIDING_WINDOW = """
    redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[2])
    if redis.call('ZCARD', KEYS[1]) >= tonumber(ARGV[3]) then
        return 0
    end
    redis.call('ZADD', KEYS[1], ARGV[1], ARGV[4])
    redis.call('EXPIRE', KEYS[1], ARGV[5])
    return 1
    """
    
    async def _check_limit(self, key: str, limit_type: str) -> bool:
        """Check if under rate limit using sliding window."""
        
        config = self.limits[limit_type]
        now = datetime.now(timezone.utc).timestamp()
        window_start = now - config["window"]
        
        # Check before ZADD: if rejected attempts were added too, a client
        # that keeps retrying would stay over the limit indefinitely
        allowed = await self.redis.eval(
            self.SLIDING_WINDOW, 1, key,
            now, window_start, config["count"],
            f"{now}:{uuid.uuid4().hex}",  # unique member per attempt
            config["window"]
        )
        
        return allowed == 1


class BackpressureManager:
    """
    Handles system backpressure during overload.
    
    Applies: Week 3, Day 3 - Backpressure
    """
    
    def __init__(self, redis_client, kafka_admin):
        self.redis = redis_client
        self.kafka = kafka_admin
    
    async def get_system_pressure(self) -> float:
        """
        Calculate current system pressure (0-1).
        
        Based on:
        - Kafka consumer lag
        - Worker queue depth
        - Database connection usage
        """
        
        # Get Kafka lag
        lag = await self._get_kafka_lag()
        max_acceptable_lag = 100000
        lag_pressure = min(1.0, lag / max_acceptable_lag)
        
        # Get worker queue depth
        queue_depth = await self.redis.llen("order_processing_queue")
        max_queue = 10000
        queue_pressure = min(1.0, queue_depth / max_queue)
        
        # Weighted average
        return (lag_pressure * 0.6) + (queue_pressure * 0.4)
    
    async def should_accept_order(self) -> Tuple[bool, str]:
        """
        Determine if we should accept new orders.
        
        Implements graceful degradation:
        - pressure < 0.7: Accept all
        - pressure 0.7-0.9: Accept, in degraded mode (skip non-essential features)
        - pressure > 0.9: Emergency mode
        """
        
        pressure = await self.get_system_pressure()
        
        if pressure < 0.7:
            return (True, "normal")
        
        if pressure < 0.9:
            return (True, "degraded")  # Maybe skip some features
        
        return (False, "System at capacity. Please try again shortly.")
Quick check3 questions · +5 XP each

Location tracking and backpressure

  1. Q1/3 Driver locations go to Redis GEO immediately, but history is buffered and written to PostgreSQL in batches of 1,000. Why?

    Answer & explanation

    Answer: C

    The hot path (current position, nearby-driver queries) needs immediate writes; history only needs to arrive eventually, so it is written behind in bulk. Batching trades a little history delay for far fewer database round trips.

  2. Q2/3 Kafka lag is 80,000 (max 100,000) and queue depth is 5,000 (max 10,000). Pressure is 0.6 x lag + 0.4 x queue. What does should_accept_order return?

    Answer & explanation

    Answer: D

    0.6 x 0.8 + 0.4 x 0.5 = 0.48 + 0.20 = 0.68, which is below the 0.7 degraded threshold. Looking at lag alone overstates pressure; the queue term pulls the weighted score down.

  3. Q3/3 Why is there a per-restaurant limit of 500 orders/hour, separate from the per-user limit?

    Answer & explanation

    Answer: B

    Per-user and per-IP limits stop abuse and the global limit protects the system; the restaurant limit protects a real-world bottleneck, the kitchen. Each layer guards a different resource, which is why there are four of them.

Deep Dive 7: Notifications, the Restaurant Socket and Driver Presence (Week 6)#

Transcript
Interviewer

"Customers, restaurants and drivers all need to hear about things. Some have the app open, some don't. How does that work, and how do you know which drivers are online?"

You

"It all goes through the notification platform from Week 6, with its three lanes and the in-app inbox. Nothing in the order flow calls APNs or an SMS provider directly."

Three Lanes for an Order's Notifications#

Lane Food-delivery examples How it goes out Under pressure
Critical Sign-in OTPs, new-device logins; driver assignment offers OTPs by SMS at once; offers over the driver's live socket, push if no ack, expiring in seconds Never shed
Transactional Order confirmed, driver assigned, driver arriving, delivered; a restaurant's new order; refunds Inbox first, then a live hint; push only if no device acks within a few seconds Degrade, but deliver
Bulk Offers and promotions Paced, never in quiet hours, dropped when they expire Throttled; never ahead of an order update

Each lane has its own topic, workers and share of provider quota, so a Friday-night promotion to every customer can't push "your driver is arriving" behind it.

Order Status as a Transactional Notification#

A status change has two audiences: the tracking screen (Deep Dive 5) and the customer's notification bell, or the lock screen when the app is closed. The bell follows Week 6's delivery rule: inbox first, then the hint, then push only if nobody acked.

pythonon_order_event · 33 lines
# services/order_notifications.py
# Applies: Week 6, Days 1-3 - lanes, idempotency keys, inbox first, ack-timeout fallback to push

from datetime import timedelta

LANES = {
    "otp":                  "critical",
    "driver_offer":         "critical",       # worthless once the offer window has passed
    "order_confirmed":      "transactional",
    "driver_assigned":      "transactional",
    "driver_arriving":      "transactional",
    "delivered":            "transactional",
    "restaurant_new_order": "transactional",
    "promo":                "bulk",
}


async def on_order_event(evt, notify):
    lane = LANES[evt.kind]
    await notify.send(
        # named after the business event, so a retried event is the same notification
        idempotency_key=f"orders:{evt.order_id}:{evt.kind}",
        user_id=evt.recipient_id,
        priority=lane,
        template=evt.kind,
        variables=evt.variables,
        # one lock-screen entry per order: "arriving" replaces "assigned"
        collapse_key=f"order:{evt.order_id}" if lane == "transactional" else None,
        expires_at=evt.at + timedelta(seconds=15) if evt.kind == "driver_offer" else None,
    )
    # The platform writes the inbox item with the next per-user sequence number,
    # hints every live connection through the registry, and pushes via APNs or
    # FCM only if no device acks within a few seconds.
  • Customer with the tracking screen open: the hint arrives over the live connection and is acked; no push.
  • App closed: no live connection, so a push at once, with the order's collapse key.
  • Phone in a lift: the socket is a zombie, no ack arrives within the timeout, so the platform pushes. When the app reconnects it resumes from the inbox and shows the same item once.

The Restaurant's Socket#

The restaurant tablet holds a WebSocket to one of the real-time gateways, and the connection registry records which one (conn:{restaurant_id} → gw-4|epoch 7). A new order is a transactional notification to the restaurant:

  1. The order is written to the restaurant's inbox with the next sequence number, before anyone is told.
  2. The delivery service looks up the registry and publishes a hint to that gateway's channel; the tablet shows the order and acks the sequence number, well inside the 2-second target.
  3. No ack within a few seconds (tablet asleep, Wi-Fi dropped): push to the restaurant app. When the tablet reconnects, it resumes "after seq N" and gets every order it missed, in order.
  4. The saga's confirmation timeout still covers a restaurant that never answers.

Gateways are deployed with slow, randomised drains, one node per zone at a time, so a deploy at 19:30 looks like a trickle of reconnects, not every kitchen going dark at once.

Driver Online Status as Presence#

Dispatch dashboards and the restaurant's "driver nearby" view show which drivers are online. That's Week 6, Day 4's presence as soft state:

  • The driver app heartbeats every d seconds; the presence entry expires after d + ε, and a delayed check at d + 2ε marks the driver offline if no newer heartbeat came. A phone that dies goes offline by itself; nobody has to say goodbye.
  • Presence is stored per device and kept in memory with a TTL, never in the orders database; "last seen" is stamped with the presence server's clock, not the phone's.
  • Viewers subscribe only to the drivers on their screen, so a driver flapping in and out of signal doesn't flood every dashboard.
  • Presence is never used for correctness. The matcher may skip drivers shown offline, but the row lock in Deep Dive 3 decides who gets an order, and reassignment mid-delivery waits for the order-level signal (no location updates for 5 minutes), not a grey dot.
Quick check3 questions · +5 XP each

Notifications, sockets and presence

  1. Q1/3 A "driver arriving" notification is acked by the customer's open tracking screen within a second. What does the platform do about mobile push?

    Answer & explanation

    Answer: B

    Order updates are transactional: inbox first, a live hint, and push only when no device acks within a few seconds. SMS is reserved for OTPs and critical sends; a routine status change never goes by SMS.

  2. Q2/3 A restaurant tablet's Wi-Fi drops for 40 seconds during the dinner rush, and two orders arrive. How does it get them?

    Answer & explanation

    Answer: D

    Each order was written to the restaurant's inbox before the hint was published, so the hint can be lost safely. No ack also triggers a push to the restaurant app in the meantime.

  3. Q3/3 A driver's phone dies mid-delivery. What happens to the driver's online dot, and what triggers reassignment?

    Answer & explanation

    Answer: A

    Presence is soft state: with no heartbeats, the entry expires after d + ε and the delayed check publishes offline. A wrong dot is cosmetic, so it never drives a correctness decision like reassigning an order.

Phase 5 · Stage 5 of 6 · 2 min

Phase 5: Scaling and Edge Cases#

Interviewer

"How would this system scale to 10x the current load?"

Scaling Strategy#

You

"Let me analyze what breaks at 10x and how we'd address it."

Scaling to 10X (50M orders/day)
  • Current: 5M orders/day, 230/sec peak
  • Target: 50M orders/day, 2,300/sec peak
Bottleneck analysis
Component Current Limit 10x Solution
API Servers 30 instances 300 instances (horizontal)
PostgreSQL Write ~10K writes/sec (peak today ~7K) Shard by region/restaurant
PostgreSQL Read 30K reads/sec More read replicas + cache
Redis 100K ops/sec Redis Cluster (10 shards)
Kafka 100K msgs/sec More partitions (320)
Location Updates 17K/sec Batch + Redis Cluster
Real-time gateways ~600K connections, ~5 nodes ~6M connections: 30 nodes at 200K, ~45 across three zones
Sharding strategy

Orders: Shard by city/region

  • NYC orders → Shard 1
  • LA orders → Shard 2
  • etc.

Drivers: Shard by operating region

Restaurants: Shard by city

Cross-shard queries minimized:

  • Orders are local to city
  • Drivers operate in one city
  • Restaurants are in one city

Edge Cases#

Interviewer

"What edge cases do you need to handle?"

Edge cases
  1. DRIVER GOES OFFLINE MID-DELIVERY
    • The online dot expires by itself (presence TTL), but that's display only
    • Detect via missing location updates (>5 min)
    • Auto-reassign to new driver
    • Notify customer with new ETA (a transactional notification)
  2. RESTAURANT CLOSES AFTER ORDER PLACED
    • Saga compensation: void payment, notify customer
    • Offer alternatives or credit
  3. CUSTOMER CANCELS AFTER FOOD PREPARED
    • If before pickup: full refund
    • If after pickup: partial refund, driver keeps food
    • Track cancellation rate per customer
  4. PAYMENT CAPTURE FAILS AFTER PICKUP
    • Food is already with driver — can't undo
    • Queue for retry with exponential backoff
    • After N failures: flag for manual review
    • Don't block delivery
  5. DOUBLE ORDER (USER CLICKS TWICE)
    • Idempotency key prevents duplicate charges
    • Dedup window: 5 minutes
  6. DRIVER AT WRONG LOCATION
    • Compare GPS with restaurant/customer address
    • Alert if >500m off
    • Allow manual override
  7. CUSTOMER'S PHONE SWITCHES FROM WI-FI TO 4G
    • The tracking connection dies silently; the app reconnects on the network-change event
    • Resume status after the last sequence number; take the next location point
  8. A GATEWAY NODE DIES AT 19:30
    • Its apps (up to 200K) reconnect elsewhere with jittered backoff, through connect admission control
    • Nothing is lost: status changes and new orders are in the logs and inboxes; unacked ones went out by push

Failure Scenarios#

Failure Detection Impact Recovery
PostgreSQL primary Health check Write failures Promote replica
Redis down Connection errors Cache miss, no rate limit Fall back to DB
Kafka broker Producer errors Event delay Buffer in memory, retry
Payment service Timeouts Can't place orders Show "payment unavailable"
Location service Missing updates Stale ETAs Show "last known"
Gateway node Lease expires, connections drop Live tracking pauses for seconds Jittered reconnect elsewhere, resume after last seq
APNs or FCM 5xx, timeouts Closed apps miss order updates Live socket for open apps; hold with an expiry, collapsed per order; ramp the drain
Quick check2 questions · +5 XP each

Scaling and edge cases

  1. Q1/2 At 10x scale, orders, drivers and restaurants are all sharded by city. Why does that keep most queries on a single shard?

    Answer & explanation

    Answer: A

    Food delivery is local: the restaurant, the driver and the customer are in one city, so the hot paths rarely cross shards. Cities are not evenly sized, which is a separate balancing problem the design would still need to handle.

  2. Q2/2 A driver stops sending location updates mid-delivery. How does the design detect and handle it?

    Answer & explanation

    Answer: C

    Location updates double as a heartbeat; their absence for over 5 minutes triggers automatic reassignment and a customer notification. Cancelling would waste food that is already picked up or ready.

Phase 6 · Stage 6 of 6 · 2 min

Phase 6: Monitoring and Operations#

Interviewer

"How would you monitor this system in production?"

Key Metrics#

Key metrics
Business metrics
Metric Target Alert if
Orders placed/minute 4,000 < 3,000
Order success rate 98% < 95%
Avg delivery time 35 min > 45 min
Driver utilization 70% < 50%
System metrics
Metric Target Alert if
API latency p99 100ms > 500ms
Kafka consumer lag < 1000 > 10,000
Database connections < 80% > 90%
Payment success rate 99% < 97%
Live connections ~600K at the dinner peak Down 5% in a minute
Push fallbacks after an ack timeout Steady 2× the same hour last week

Monitoring Dashboard#

Food delivery operations

Order health

  • Orders/min: 3,847
  • Success rate: 98.2%
  • Avg delivery time: 32 min

System health

  • API p99 latency: 87ms
  • Kafka lag: 342
  • DB connections: 62%

Payments

  • Auth success: 99.1%
  • Capture success: 99.8%
  • Pending captures: 127

Driver fleet (50,234 active)

  • Available: 18,421
  • On delivery: 31,813
  • Avg orders/driver: 2.3

Alerting Strategy#

Alerting strategy

CRITICAL (Page immediately)

  • Order success rate < 90%
  • Payment failures > 5%
  • All drivers in city unavailable
  • Database primary down
  • Live connections down 5% in a minute

WARNING (Slack, investigate)

  • Order success rate < 95%
  • Kafka lag > 10,000
  • Driver assignment > 60 sec avg
  • Payment auth latency > 2s

INFO (Dashboard only)

  • Peak traffic approaching
  • New restaurant onboarding
  • Scheduled maintenance window

Runbook: High Order Failure Rate#

Runbook: Order Success Rate Dropped
Symptoms
  • Order success rate alert triggered
  • Customer complaints in support queue
Diagnosis
  1. Check by failure reason:

    sql3 lines
    SELECT reason, COUNT(*) FROM order_failures 
    WHERE created_at > NOW() - INTERVAL '1 hour'
    GROUP BY reason;
  2. Check payment health:

    • Stripe status page
    • Payment auth latency metrics
  3. Check restaurant connectivity:

    • Restaurant WebSocket connection count, per gateway
    • Unacked new orders falling back to push
    • Restaurant app errors
Resolution
  • If payment issue: Enable backup processor
  • If restaurant app: Check for app store update issues
  • If driver shortage: Expand driver radius
  • If system overload: Enable degraded mode
Quick check2 questions · +5 XP each

Monitoring and operations

  1. Q1/2 Order success rate drops to 93%. Under the alerting strategy, what happens?

    Answer & explanation

    Answer: A

    Below 95% is a warning (Slack, investigate); below 90% is critical and pages. Tiering keeps pages for situations that need someone right now. Degraded mode is a resolution step in the runbook, not an automatic reaction to this alert.

  2. Q2/2 The runbook's first diagnosis step groups recent order failures by reason. Why start there?

    Answer & explanation

    Answer: D

    Each cause has a different remedy, from a backup payment processor to a wider driver radius, so identifying the dominant reason comes first. Acting before you know the cause risks applying the wrong fix.

Wrap-up · 2 min

Interview Conclusion#

Transcript
Interviewer

"Excellent work. You've covered a lot of ground. Let me ask one final question — if you had to build this from scratch, what would you build first?"

You

"I'd prioritize in this order:

  1. Core order flow with transactional outbox — this is the money path
  2. Payment integration with idempotency — can't lose money
  3. Restaurant notification — orders need to get to kitchens
  4. Basic driver assignment — start with simple nearest-driver
  5. Customer notifications — keep users informed

The saga orchestration, real-time tracking, and advanced driver matching can come in phase 2. The key is getting orders reliably from customers to restaurants first."

Interviewer

"Great prioritization. Any questions for me?"

You

"Yes — what's been the biggest operational challenge you've faced at scale? I'm curious what breaks that you don't expect."

Summary: Weeks 1-6 Concepts Applied#

Week 1: Data at Scale#

Concept Application
Partitioning Orders sharded by region, drivers by city
Replication PostgreSQL read replicas for order queries
Rate Limiting Multi-layer: user, IP, restaurant, global
Hot Keys Driver locations in Redis GEO

Week 2: Failure-First Design#

Concept Application
Timeouts Restaurant confirmation timeout (5 min)
Idempotency Payment auth/capture with idempotency keys
Circuit Breakers Payment service circuit breaker
Retry Strategies Exponential backoff for payment capture

Week 3: Messaging and Async#

Concept Application
Transactional Outbox Order creation → Kafka publishing
Consumer Groups Order workers, notification workers
Backpressure System pressure monitoring
Dead Letter Queue Failed payment captures

Week 4: Caching#

Concept Application
Hot data in memory Current driver locations in Redis GEO
Write-Behind Location history batching
TTL expiry Idempotency results (24 h) and presence entries
Multi-tier Redis for hot, PostgreSQL for cold

Week 5: Consistency and Coordination#

Concept Application
Saga Pattern Order lifecycle orchestration
Compensation Payment void, driver release on failure
Row Locking + Version Check Driver assignment race prevention
Distributed Locks Idempotency key locking

Week 6: Real-Time Delivery#

Concept Application
Priority lanes Critical (OTPs, driver offers), transactional (order updates, new orders), bulk (promotions)
Inbox first Order updates and restaurant orders stored with a sequence number before any hint or push
Push as the fallback Order updates pushed only when no live device acks within a few seconds
Real-time tier SSE or WebSocket for live tracking, ~600K connections at the dinner peak
Coalescing and resume Location coalesced to the latest point; status resumed after the last sequence number
Connection registry Finds the gateway holding a customer's or restaurant's app
Presence Drivers' online status as soft state with a TTL

Self-Assessment Checklist#

After studying this capstone, verify you can:

After studying this capstone, verify you can0/15 ready
  • Design a transactional outbox for reliable event publishing
  • Implement idempotency for payment operations
  • Use a row lock plus a version check to prevent double assignment
  • Design a saga with compensation for multi-step workflows
  • Implement rate limiting at multiple layers
  • Handle backpressure during system overload
  • Use Redis GEO for spatial queries
  • Design caching strategies for high-frequency updates
  • Estimate storage and traffic for a large-scale system
  • Size a real-time tier by connections, and push live tracking instead of polling
  • Route notifications through priority lanes, inbox first, with push as the fallback
  • Use presence as soft state without relying on it for correctness
  • Create monitoring dashboards and alerting strategies
  • Analyze failure scenarios and recovery procedures
  • Prioritize features for MVP vs later phases

This capstone integrates all concepts from Weeks 1-6 of the System Design Mastery Series. Use this as a template for approaching similar interview problems involving transactional workflows, real-time updates, and distributed coordination.

this lesson in shorts · 4

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Boss · Food Delivery Order System
Finish BossStages clear as you read them (0/6 so far). Skipping ahead doesn't count: marking complete unlocks after ~7 more min of reading.

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

SwiggyBlog post · ~5 min

Re-Architecting Swiggy's logistics systems

Ravi Singh · 2021
What
Swiggy breaks up the 'delivery monolith' behind its three-sided food marketplace.
Key idea
The old model treated every delivery as one pick-up and one drop; the new model of tasks, jobs, job legs and trips supports multi-leg deliveries across Food, Stores and Genie.
Why here
The capstone's order and driver-assignment flows: model delivery as a state machine of tasks so new business lines reuse it.
taught here: Delivery order state machine

Kept from the earlier trail (checked 2026-10-18); medium.com answered the 2026-10-29 automated re-check with 403.

UberBlog post · ~19 min

Uber's Fulfillment Platform: Ground-up Re-architecture to Accelerate Uber's Go/Get Strategy

Ashwin Neerabail, Kamran Massoudi et al. · 2021
What
How Uber rebuilt the platform that tracks every trip and courier session.
Key idea
The old design favoured availability, with Ringpop serialising writes per key; the new one moved trip state to a NewSQL store with strongly consistent transactions across rows and tables.
Why here
The capstone's distributed-coordination deep dive: who owns a driver's state, and why consistency mattered more as products multiplied.
taught here: Driver assignment and coordination
UberBlog post · ~19 min

Uber's Real-Time Push Platform

Madan Thangavelu, Anirudh Raja et al. · 2020
What
How Uber replaced its apps' polling for trip state with RAMEN, a platform that pushes over long-lived connections.
Key idea
Polling calls had become most of the API gateway's traffic; pushing state changes over persistent connections, with sequence numbers to resume, removed them.
Why here
The capstone's live tracking: one connection per tracking screen instead of 40,000 polls a second, with status resumed after the last sequence number.
taught here: Live tracking instead of polling

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…