At a glance#
| Number | Value | What it drives |
|---|---|---|
| Opening concurrency | 6.3M users in a minute (2026) | forecast and pre-scale; warm every cache before midnight |
| Transactions | 6–8× business-as-usual | size each tier by its own multiplier |
| Payment requests | ~1,000× normal at the peak | per-bank breakers; unknown outcomes reconciled |
| Requests | ~3.2M / s at the edge · ~630k / s at services (est.) | the read plane is served from the CDN and memory |
| Orders | ~1,200 / s at the opening · design 5,000 (est.) | ~3,600 stock holds a second |
| One hot deal | 5,000 units · ~30,000 taps / s (est.) | a line per deal; ~6,500 tokens |
| Shipments | ~4M a day · 7.3M on the BBD 2025 peak day | delivery dates from wave capacity |
| Database load | 95M transactions / s during TBBD (Aerospike, Nov 2024) | page lookups fan out; keep them in memory |
Architecture#
DiagramA cached read plane, a small exact write plane
%% title: A cached read plane, a small exact write plane
flowchart TD
APP@{ icon: "sd:phone", label: "Flipkart app and site<br/>browse · deals · checkout" } ==> CDN@{ icon: "sd:cdn", label: "CDN<br/>pages · deal states" }
APP ==> GW@{ icon: "sd:gateway", label: "Gateway<br/>tiers · shedding · bot checks" }
GW --> SRCH@{ icon: "sd:search", label: "Search<br/>index without prices" }
GW --> PRICE@{ icon: "sd:cache", label: "Price book cache<br/>versioned · fail closed" }
GW ==>|"Buy now"| ADM["Deal line<br/>tokens · lottery window"]:::hot
ADM ==> INV["Stock holds<br/>expiring · keyed by checkout"]:::hot
INV --> KV@{ icon: "sd:database", label: "Stock and order stores<br/>strongly consistent" }
INV ==> CHK["Checkout and orders<br/>idempotent · outbox"]:::core
CHK ==> PAY["Payments<br/>breakers · unknown outcomes"]:::core
PAY ==> BANK@{ icon: "sd:bank", label: "Gateways and banks<br/>UPI · cards · EMI" }
CHK -->|"reserve a slot"| PROM["Promise engine<br/>wave capacity · cut-offs"]:::core
CHK ==> BUS@{ icon: "sd:queue", label: "Message bus<br/>priority lanes" }
BUS ==> FC@{ icon: "sd:warehouse", label: "Fulfilment centres<br/>waves · trucks" }
class APP client
class CDN,GW edge
class SRCH service
class PRICE cache
class KV store
class BANK external
class BUS queue
class FC store- Two planes: pages, prices, search, stock badges and dates may be seconds stale; admission, holds, orders, payments and slots are exact.
- The sale is a version: prices, offers and deal states are built and pushed ahead; midnight is one pointer flip.
- Every hint has a commitment: "In stock" becomes a hold, the shown price is the charged price or lower, "Delivery by" becomes a reserved slot.
DiagramOne tap on a deal: answered at the edge unless it can succeed
%% title: One tap on a deal: answered at the edge unless it can succeed
flowchart TD
TAP["Buy now on a deal<br/>customer · device · address"]:::service
BOT{"Limits and bot checks pass?"}
WIN{"Inside the 2 s lottery window?"}
LOT["Random order among<br/>the first taps"]:::service
TOK{"Tokens issued below<br/>units × 1.3?"}
GIVE@{ icon: "sd:ticket", label: "Signed token<br/>3 minutes · one per customer" }
WAIT["In line<br/>position shown"]:::service
SOLD@{ icon: "sd:cdn", label: "Sold out<br/>published once" }
TAP ==> BOT
BOT -->|"no"| WAIT
BOT ==>|"yes"| WIN
WIN -->|"yes"| LOT ==> TOK
WIN -->|"no"| TOK
TOK ==>|"yes"| GIVE
TOK -->|"no"| WAIT
WAIT -.->|"tokens all used"| SOLD
class GIVE ok
class SOLD edgeKey flows#
DiagramRohan buys the deal phone at 12:00:01
%% title: Rohan buys the deal phone at 12:00:01 sequenceDiagram autonumber participant A as Rohan's app participant E as Edge and deal line participant C as Checkout participant I as Inventory participant P as Payments participant B as Bank A->>E: Buy now, deal 4471 E-->>A: Token, valid 3 minutes A->>C: Checkout with token, key k1 C->>I: Hold 1 unit for 10 minutes, key k1 I-->>C: Held C->>C: Order PENDING_PAYMENT · slot for 13 Oct held A->>P: Pay by UPI, key k1 P->>B: Collect request B-->>P: Success P->>C: Paid C-->>A: Order confirmed Note over C: confirmed state and outbox row commit together · bus to the warehouse
DiagramA double tap and an unknown payment, without a double charge
%% title: A double tap and an unknown payment, without a double charge
sequenceDiagram
autonumber
participant A as App
participant C as Checkout
participant P as Payments
participant G as Gateway
participant R as Reconciler
participant I as Inventory
A->>C: Pay, key k1
C->>P: Charge o77, key k1
P->>G: UPI collect
G-->>P: No answer in 30 s
P->>C: o77 PAYMENT_UNKNOWN
C->>I: Extend hold k1, within a cap
A->>C: Pay again, key k1
C-->>A: In progress · no new charge
R->>G: Status of o77?
alt success
G-->>R: Success
R->>C: CONFIRMED · outbox row
C->>I: Hold becomes a sale
else failed
G-->>R: Failed
R->>C: PAYMENT_FAILED
C->>I: Release the unit
endDiagramThe midnight switch, built ahead and flipped once
%% title: The midnight switch, built ahead and flipped once sequenceDiagram autonumber participant O as Sale ops participant B as Version builder participant K as Cache nodes participant D as CDN participant A as Apps O->>B: Stage v42 · prices, offers, deal states B->>B: Guardrails · second approval B->>K: Load v42 under new keys B->>D: Push v42 pages, not live Note over K,D: v41 still live until 12:00:00 B->>K: 12:00:00 · active version v41 to v42 A->>D: Refresh with 0 to 3 s jitter D-->>A: Sale pages v42
Data model#
DiagramCore entities
%% title: Core entities
classDiagram
class PriceBook {
+version : string
+activeFrom : time
+prices : sku to price
+offers : sku to bank offers
}
class Deal {
+dealId : string
+units : int
+tokenCap : units x 1.3
+startsAt : time
}
class Token {
+customerId : string
+dealId : string
+expires : 3 minutes
}
class StockCounter {
+sku : string
+fc : string
+available : int
+held : int
+sold : int
}
class Hold {
+key : checkout id
+state : HELD / PAYING / SOLD / RELEASED
+expires : 10 minutes, capped while paying
}
class Order {
+orderId : string
+key : idempotency key
+state : string
+promisedDate : date
}
class WaveSlot {
+fc : string
+cutoff : time
+capacity : int
+urgentShare : float
}
Deal "1" --> "many" Token : admits
Token "1" --> "0..1" Hold : lets you try
StockCounter "1" --> "many" Hold : held from
Order "1" --> "1" Hold : key k1
Order "many" --> "1" WaveSlot : reserves
PriceBook "1" --> "many" Order : priced by- Stock is counted per SKU per fulfilment centre: available + held + sold = on hand, checked by a reconciler every few minutes; deal units are a separate pool.
- Prices are keyed by version: the live book and the next one sit side by side in every cache node (v41 live, v42 loaded), so the flip needs no purge.
Lifecycle#
DiagramAn order, from checkout to delivery
%% title: An order, from checkout to delivery stateDiagram-v2 [*] --> PENDING_PAYMENT: checkout · stock and slot held PENDING_PAYMENT --> CONFIRMED: paid PENDING_PAYMENT --> PAYMENT_UNKNOWN: no answer PENDING_PAYMENT --> PAYMENT_FAILED: declined or hold expired PAYMENT_UNKNOWN --> CONFIRMED: reconciler · success PAYMENT_UNKNOWN --> PAYMENT_FAILED: reconciler · failed CONFIRMED --> PACKED: picked in its wave PACKED --> SHIPPED: on the truck SHIPPED --> DELIVERED: by the promised date DELIVERED --> [*] PAYMENT_FAILED --> [*] class DELIVERED ok class PAYMENT_FAILED bad class PAYMENT_UNKNOWN queue
Decisions#
| Decision | Chose | Over | Because |
|---|---|---|---|
| Midnight switch | Stage a new version, flip one pointer | Cached prices expiring at midnight | synchronised expiry is a thundering herd |
| Deal access | A line per deal, tokens ≈ units × 1.3 | First-come at the database | 30,000 writes / s on one key, ~98% fail |
| Queue scope | Only the deal's Buy button | A site-wide waiting room | the scarce thing is one product, not the store |
| Fairness | 2 s lottery window, then first-come | Pure arrival order | fibre beats 4G by hundreds of ms |
| When to take stock | Expiring hold at checkout start | At add-to-cart or at payment | carts lock unbought stock; payment-time takes cause cancellations |
| Payment timeouts | UNKNOWN, reconciled by status | Retry as a new charge | a timed-out charge may have succeeded |
| Missing price | Fail closed, no Buy button | Fall back to list price | a wrong price costs trust |
| Delivery date | Reserve wave capacity, urgent trucks first | FIFO, static dates | bumped next-truck orders lose a day |
When it breaks#
| Failure | What users see | Contain it with |
|---|---|---|
| Caches cold or expiring at midnight | slow or failing pages at 12:00 | warm ahead; versioned flip; app jitter; shed tier 3 |
| One bank slows at the peak | card payments hang | per-bank breaker; demote its cards behind UPI; bulkheads |
| Gateway stops answering | "payment pending" | UNKNOWN state; extended holds; reconciler; settlement file |
| Deal priced ₹999, not ₹9,999 | a too-good deal | build guardrails; per-deal kill switch; policy decided in advance |
| A price cache node comes back empty | "price unavailable" briefly | refill from the active version; never a default |
| A fulfilment centre goes dark | later dates; some orders re-planned | wave capacity to zero; route to other centres; tell customers early |
Say it in the interview#
- "The sale is a version, not an event": build prices, offers and deal states ahead and flip one pointer at midnight, so nothing expires together.
- Queue the deal, cache the store: a line wraps one Buy button; the other 150 million products stay open.
- Fair-ish beats first: a 2-second lottery window erases the network advantage; then tokens at about 1.3× the units.
- Hold, then take money: an expiring, keyed hold at checkout against the real count; a page's "In stock" is only a hint.
- "Unknown is a state": a timed-out charge is reconciled, never retried as new; one key from checkout to payment.
- Fail closed on price: no price, no Buy button; charge the price shown or lower.
- Dates are capacity: reserve a slot in a wave at checkout, protect urgent trucks, and move the page's date before the order, not after.