~learn.

Side quest 27Sale-day commerce

Flipkart Big Billion Days.

Six Million Shoppers in the Same Minute, Five Thousand Phones at One Price, and Every Order Has to Be Real

ADVANCED49 MIN READ6 PHASES · 45 MIN INTERVIEW+120 XP

Mission brief

Design the e-commerce platform for India's biggest sale event

Quest log0/7 stages · 0/9 checkpoints · 0/8 quick checks
concurrent users in a minute in the opening hour of The Big Billion Days 2026 (Flipkart, October 2026)
6.3 million
the usual transaction volume during the sale (Flipkart and Google Cloud, 2023)
6–8×
the payment requests of normal business at the festive peak (Flipkart's vice-president of payments to Business Standard, September 2026, reported by DQIndia)
~1,000×
database transactions a second sustained on Flipkart's Aerospike platform during The Big Billion Days (Aerospike press release, November 2024)
95 million

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

watch it first · newThe whole design in a 17-minute animated lecture4 quiz checkpoints · ask the AI about any moment · captions, chapters and speed
Optional · timed run · 6 phases · 45 min

Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.

Intro · 4 min

How Do You Open a Shop to Six Million People in the Same Minute, and Sell Five Thousand Phones Exactly Once Each?

It is 11:58 pm on 7 October 2026 in Bhagalpur. Rohan Mandal, a final-year engineering student, has had the same phone in his wishlist for three weeks. His Plus membership lets him into the sale at midnight, a day before everyone else. He has the app open on the phone's page and his UPI app logged in. The deal says ₹9,999 and "limited stock". He doesn't know how limited: 5,000 units, for everyone in India who has the same page open.

At 12:00:00 the button changes from "Notify me" to "Buy now". Rohan taps it at 12:00:01, and so do a few hundred thousand other people, from Bengaluru flats on fibre and from towns on one bar of 4G. In the next ten seconds something has to decide which 5,000 of them get the phone, tell everyone else the truth quickly, take money only from those 5,000, and never sell unit 5,001. Rohan also adds a washing machine for his parents before Diwali. The page says "Delivery by Tue, 13 Oct", and that has to stay true after millions of orders land on the same warehouses tonight.

Flipkart, from published numbers
  • 6.3 million concurrent users in a minute in the opening hour of TBBD 2026; Early Access, which opened at midnight on 8 October, drew 287 million+ sessions, with "over 6x more Indians shopping on the platform than in a regular hour" (Flipkart, 9 October 2026)
  • 6–8× the business-as-usual transaction volume during the sale; the 2023 sale ran at 7× the usual transactional data (Flipkart and Google Cloud, October 2023)
  • ~1,000× more payment requests at the festive peak than in normal business; biometric-authenticated transactions had a 12–15% higher success rate than OTP-based ones (Gaurav Arora, Flipkart's vice-president of payments, to Business Standard in September 2026, as reported by DQIndia)
  • 95 million transactions a second sustained on Flipkart's in-house Aerospike platform during The Big Billion Days (Aerospike press release, November 2024); 90 million queries a second in aggregate from 50+ use cases, including search, pricing and inventory, across 3 data centres (Flipkart engineers' talk, as written up by Aerospike, November 2025)
  • 7.3 million shipments fulfilled in a single day and 5,000+ a minute on the peak days of BBD 2025 (Flipkart, December 2025), against ~4 million on an average day (Flipkart Tech, 2026)
  • 500 million registered users and 150 million products (Flipkart, 2026)

For context:
Flipkart publishes sale-day traffic, payments and supply-chain numbers and parts of its infrastructure; it has not published how it queues buyers for a deal, reserves stock or runs checkout, so those parts are labelled as our design
Every number labelled as an assumption is illustrative

This is the system we'll design today.

ExplainerThe whole quest: Flipkart Big Billion Days

Flipkart reported 6.3 million concurrent users in a minute in the opening hour of The Big Billion Days 2026; the sale brings 6 to 8 times the usual transactions and about 1,000 times the payment requests. On stated assumptions that is ~3.2 million requests a second at the edge, ~630,000 reaching services, ~1,200 orders a second at the opening (designed for 5,000) and ~30,000 taps a second on one 5,000-unit deal. The sale is staged as a version of prices, offers, deal states and pages pushed to every cache and the CDN, and at 12:00:00 one pointer flips. Only a deal's Buy now button queues: admission at the edge orders the first 2 seconds of taps at random and issues tokens for units × 1.3, one per customer. Checkout holds a unit for 10 minutes, extended while money is in flight, and carries one idempotency key to the bank; a timeout is unknown, not failed, and is reconciled, the paid state and an outbox row commit in one transaction, and a breaker per bank demotes a slow issuer. The delivery date is a slot reserved in a warehouse wave, so as the 12:30 wave fills, new viewers see a later date. The deep dives end with a ₹999 typo, a fulfilment centre going dark and Day 1 a month before Diwali.

The Interview Begins#

You're interviewing at an e-commerce company. The interviewer shares two screenshots from the same phone: a product page at 12:00:00 with a red "Buy now" button and "Only a few left", and an order confirmation at 12:00:41 reading "Order placed · Delivery by Tue, 13 Oct".

Interviewer

"Forty seconds separate these two screens, and in those forty seconds a few million other people were doing the same thing. Design the platform that runs the sale: the opening, the deals, the cart and checkout, the payments, browsing and search, and the delivery date."

Design the e-commerce platform for a national sale event

For about a week each autumn, a marketplace with hundreds of millions of users runs a sale with deep discounts. Members get in at midnight a day early, then everyone. Some offers are limited-quantity deals that go live at fixed times. Traffic, orders and payments jump to many times a normal day within minutes.

Requirements
  • Open the sale at a fixed moment: prices, offers and deal pages switch for everyone together
  • Sell limited-quantity deals fairly and fast, and never sell more units than exist
  • Carts, checkout and payments that hold at many times the usual rate
  • Browse and search 150 million products while the sale is on
  • A delivery date on every page and order that the supply chain can keep
Interviewer

"Leave out seller onboarding, ads, the recommendation models, returns and the 10-minute grocery service. Assume a payment gateway exists; you decide how checkout uses it."

Phase 1 · Stage 1 of 7 · 5 min budget · 2 min read

Phase 1: Requirements Clarification (5 minutes)#

You

"A sale is a planned spike with a hard start time, so I'll ask about the shape of the load first, then about what must never go wrong."

Your Questions#

Transcript
You

"How big is the opening?"

Interviewer

"Flipkart reported 6.3 million concurrent users in a minute in the opening hour of the 2026 sale, with over six times as many people shopping as in a regular hour. Early Access opened at midnight on 8 October for Plus and Black members and drew more than 287 million sessions."

You

"And the work behind the traffic?"

Interviewer

"A Flipkart architect and Google Cloud wrote in 2023 that the sale brings a transaction volume surge of six to eight times business-as-usual. Payments are steeper: Flipkart's vice-president of payments told Business Standard in September 2026, as DQIndia reported, that it handles nearly 1,000 times more payment requests at the festive peak than in normal business."

You

"What's a deal, physically?"

Interviewer

"A product at a sale price with a fixed number of units, going live at a set time. Flipkart hasn't published how it runs them. For scale: in a 2014 flash sale on Flipkart, 40,000 phones sold out in 4.2 seconds with more than 250,000 people registered."

You

"Where does the delivery date come from?"

Interviewer

"Flipkart's engineers describe a Promise Engine that computes the date from processing capacity across its network and truck schedules. On an average day Flipkart handles about 4 million shipments across 19,000 pincodes; on a peak day of the 2025 sale it shipped 7.3 million."

You

"And the correctness bar?"

Interviewer

"Never sell what you don't have. Never charge a price other than the one shown. Never charge without an order, or twice for one. And the date you promise should be one you can keep."

Functional Requirements#

Functional requirements
  • Sale opening: each phase (Early Access, Day 1) activates at a set time; prices, offers and deal states switch together
  • Deals: a start time, a unit count and a per-customer limit; buyers learn quickly whether they got one
  • Inventory: held at checkout, sold on payment, released on expiry or failure; never more sold than exist
  • Checkout and payments: UPI, cards, EMI, pay-later; a retry never charges twice; unknown outcomes are resolved, not guessed
  • Browse and search: pages for 150 million products with sale prices, offers and stock badges
  • Delivery promise: a date per product and pincode on the page, and a committed date on the order

Non-Functional Requirements#

Non-functional requirements
Peak (published and estimated)
  • 6.3 million concurrent users in a minute (published); ~3.2 million requests a second at the edge (estimated below)
  • ~1,200 orders a second in the first minutes, designed for 5,000 (estimated)
  • ~30,000 "Buy now" taps a second on one hot deal (estimated)
Latency (design targets)
  • Product and search pages p99 < 300 ms at the edge
  • Reserve-and-checkout p99 < 500 ms; a deal verdict ("you're in" or "sold out") in < 2 s
Correctness
  • Zero units sold beyond stock; zero duplicate charges
  • The price charged is the price shown at checkout, or lower
  • ≥ 95% of sale-week orders delivered by the promised date (design target)

Phase 2 · Stage 2 of 7 · 5 min budget · 3 min read

Phase 2: Back of the Envelope Estimation (5 minutes)#

You

"The anchors are published: 6.3 million concurrent users in a minute, 6–8× the usual transactions, 7.3 million shipments on a peak day. Flipkart doesn't publish request rates or orders a second, so the rest are my labelled assumptions."

Your turn · checkpoint 1not answered

Before the numbers: what does a concurrent user cost, and what does one deal cost?

6.3 million people are on the app in the same minute, each app making an API call every two seconds; 80% can be answered at the edge. How many requests a second reach your services? Then one deal: 5,000 units, 300,000 taps in ten seconds. How many writes a second hit that product's stock if every tap goes to the database, and how many can succeed?

Think it through, or jot it down. Then compare.

One strong answer

  • Requests: 6.3 million × 0.5 a second ≈ 3.2 million a second at the edge. With 80% served by the CDN and edge caches, ~630,000 a second reach services; design for 2×.
  • The deal: 300,000 ÷ 10 s = 30,000 writes a second on one key, of which at most 5,000 can ever succeed.
  • So the read side is huge but forgiving, the write side small but exact.

The Opening Minutes#

WorksheetTraffic at the opening (assumptions labelled)
Value
Concurrent users in a minute, opening hour (published, 2026) 6.3 million
API calls per open app (assumed: one every 2 s) 0.5 a second
Requests at the edge ~3.2 million a second
Share answered at the CDN and edge caches (assumed) ~80%
Requests reaching services ~630,000 a second; design for ~1.3 million
Database transactions a second during TBBD (published by Aerospike, November 2024) 95 million

Conclusion: if the edge estimate is right and most of that database load came from these requests, 95 million database transactions a second would be ~30 lookups per edge request, or ~150 behind each request that reaches a service (price, offers, stock, delivery date, recommendations, ads); only an inference, since the numbers come from different years and the database also serves other work. Either way, the read path must be served from memory.

Orders and Payments#

WorksheetOrders, reservations and payment calls (assumptions labelled)
  • Peak shipping day (published, BBD 2025): 7.3 million shipments
  • Orders on the opening day (assumed 1.5× the peak shipping day): ~11 million
  • Share in the opening hour (assumed 10%): ~1.1 million → ~300 orders a second on average
  • First five minutes (assumed 4× the hour's average): ~1,200 orders a second; design for 5,000
  • Checkout starts per completed order (assumed 3): abandoned carts, failed payments, sold-out deals → ~3,600 stock reservations a second at the opening peak
  • Payment calls per order (assumed ~4): create, authenticate, confirm, plus retries → ~5,000 a second at the opening peak, ~20,000 at the design point

Conclusion: orders are tiny next to page views, but each touches stock, a delivery slot and a payment, and payments go to banks we don't control.

One Flash Deal#

WorksheetA 5,000-unit deal at 12:00:00 (assumptions labelled)
  • Units: 5,000; page open at 12:00 (assumed): ~500,000 people
  • Taps in the first 10 seconds (assumed 60%): 300,000 → ~30,000 a second on one product
  • Published comparison: 40,000 units in 4.2 s in 2014 ≈ ~9,500 units sold a second
  • Without admission: 30,000 conditional writes a second on one stock key; ~295,000 of them fail
  • With admission tokens (Deep Dive 2): ~6,500 tokens (5,000 × 1.3), so ~6,500 reservations in total

Conclusion: the deal's real load is at the front door, not in the inventory service. Turn the herd away before it reaches the stock.

Key Metrics Summary#

Metric Value
Concurrent users in a minute (Flipkart, 2026) 6.3 million
Transaction volume vs usual (Flipkart and Google Cloud) 6–8×
Payment requests vs usual (Flipkart's VP of payments, via DQIndia, 2026) ~1,000×
Requests at the edge / at services (estimated) ~3.2 million / ~630,000 a second
Orders at the opening peak (estimated) / design ~1,200 / 5,000 a second
Taps on one hot deal (estimated) ~30,000 a second
Products (Flipkart) 150 million
Shipments, average / BBD 2025 peak day (Flipkart) ~4 million / 7.3 million
Quick check2 questions · +5 XP each

Requirements and estimation

  1. Q1/2 6.3 million open apps each make an API call every two seconds. Roughly how many requests a second reach the edge?

    Answer & explanation

    Answer: B

    6.3 million × 0.5 a second ≈ 3.2 million. With 80% served at the edge, ~630,000 reach services.

  2. Q2/2 Why can't the delivery date on a product page be computed per request?

    Answer & explanation

    Answer: B

    The page shows a cached date; the order reserves a slot. The same split as stock: a hint on the page, a reservation at checkout.

Phase 3 · Stage 3 of 7 · 10 min budget · 4 min read

Phase 3: High-Level Design (10 minutes)#

Your turn · checkpoint 2not answered

Sketch it first: Rohan's 12:00:01 tap

Rohan taps "Buy now" one second after midnight. What has to be already in place for the page he's looking at to show ₹9,999 and not the old price? Which component decides whether he is one of the 5,000, and what does it hand him? Where is a unit taken out of stock, and when does it go back if he doesn't pay? Then he pays by UPI: what links the payment to his order so a double tap can't charge twice, and what tells the warehouse to pick his phone?

Think it through, or jot it down. Then compare.

One strong answer

  • Before midnight: the sale's prices, deal states and pages were staged as a new version and pushed to caches and the CDN; at 12:00:00 one pointer flips. Nothing is recomputed at midnight.
  • Deal admission at the edge gives Rohan a short-lived token, one per customer, if he's within the deal's admission count; everyone else is told "in line" or "sold out" without touching stock.
  • Inventory takes one unit when checkout starts, as a hold that expires (say in 10 minutes), keyed by his checkout id.
  • Checkout creates the order in PENDING_PAYMENT with an idempotency key that the payment carries, so a second tap finds the first attempt. On success the order is CONFIRMED, the hold becomes a sale, and an event goes to fulfilment over the message bus.
  • The delivery date on his order is a slot reserved in a warehouse wave at checkout, not the page's cached date.
You

"Three ideas carry this design. A read plane that is huge, cached and allowed to be a little stale: pages, prices, search, stock badges, delivery estimates. A write plane that is small and exact: admission, reservation, orders, payments, delivery slots. And a sale clock: everything that changes at midnight is built in advance as a new version, and midnight is one switch."

System Architecture#

DiagramA sale platform: a cached read plane, a small exact write plane, and one switch at midnight
%% title: A sale platform: a cached read plane, a small exact write plane, and one switch at midnight
flowchart TD
  subgraph L1["Layer 1 · People"]
    direction LR
    APP@{ icon: "sd:phone", label: "Flipkart app and site<br/>browse, deals, checkout" }
    OPS@{ icon: "sd:users", label: "Sale ops and sellers<br/>deals, prices, stock" }
  end
  subgraph L2["Layer 2 · Edge"]
    direction LR
    CDN@{ icon: "sd:cdn", label: "CDN<br/>pages, images, deal states" }
    GW@{ icon: "sd:gateway", label: "Gateway<br/>tiers, shedding, bot checks" }
  end
  subgraph L3["Layer 3 · Read plane"]
    direction LR
    SRCH@{ icon: "sd:search", label: "Search and browse<br/>index without prices" }
    PRICE@{ icon: "sd:cache", label: "Price and offer cache<br/>versioned price books" }
    PROM["Promise engine<br/>date per pincode, wave capacity"]:::core
  end
  subgraph L4["Layer 4 · Write plane"]
    direction LR
    ADM["Deal admission<br/>tokens per deal"]:::hot
    INV["Inventory<br/>holds with expiry"]:::hot
    CHK["Checkout and orders<br/>idempotent state machine"]:::core
    PAY["Payments<br/>routing, unknown outcomes"]:::core
  end
  subgraph L5["Layer 5 · State and partners"]
    direction LR
    KV@{ icon: "sd:database", label: "Stock and order stores<br/>strongly consistent" }
    BUS@{ icon: "sd:queue", label: "Message bus<br/>priority lanes" }
    BANK@{ icon: "sd:bank", label: "Gateways and banks<br/>UPI, cards, EMI" }
    FC@{ icon: "sd:warehouse", label: "Fulfilment centres<br/>waves and cut-offs" }
  end
  APP ==> CDN
  APP ==> GW
  OPS -.->|"staged before midnight"| PRICE
  GW --> SRCH
  GW --> PRICE
  GW --> PROM
  GW ==>|"Buy now"| ADM
  ADM ==> INV
  INV ==> CHK
  CHK ==> PAY
  PAY ==> BANK
  INV --> KV
  CHK --> KV
  CHK -->|"reserve a slot"| PROM
  CHK ==> BUS
  BUS ==> FC
  class APP client
  class OPS external
  class CDN,GW edge
  class SRCH service
  class PRICE cache
  class KV store
  class BUS queue
  class BANK external
  class FC store

KEY INSIGHTS:

  • Two planes, two rules. The read plane may be seconds stale and is served from the CDN and memory; the write plane is exact and small, and nothing in it trusts a cached number
  • The sale is a version, not an event. Prices, deals and pages for each phase are built and pushed before it starts; the start is one pointer flip
  • Every hint has a commitment behind it. "In stock" on the page becomes a hold at checkout; "Delivery by 13 Oct" becomes a reserved slot in a warehouse wave; the price shown is the price charged

Data Flow: Rohan's Midnight Purchase#

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 admission
  participant C as Checkout
  participant I as Inventory
  participant P as Payments
  participant B as Bank
  A->>E: Buy now, deal 4471, at 12:00:01
  E->>E: Within admission count, one per customer
  E-->>A: Token t93, valid 3 minutes
  A->>C: Checkout with t93, 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, key k1
  C-->>A: Order confirmed

MEANWHILE, IN PARALLEL:

  • Unpaid holds expire after 10 minutes and unused tokens after 3, admitting the next people in line; when both run out, the deal flips to "sold out" on the CDN
  • The confirmed order goes over the message bus to the fulfilment centre, which picks it in the wave its slot was reserved in
Quick check2 questions · +5 XP each

High-level design

  1. Q1/2 The product page shows "In stock" from a cache that is three seconds old. Is that a bug?

    Answer & explanation

    Answer: B

    The page may be stale; the reservation is exact. Overselling happens only if something sells from the hint.

  2. Q2/2 Rohan's UPI app times out and he taps "Pay" again. What stops a double charge?

    Answer & explanation

    Answer: B

    One key from checkout to payment, so a retry is a lookup, not a new charge.

Phase 4 · Stage 4 of 7 · 20 min budget · 23 min read

Phase 4: Deep Dives (20 minutes)#

Deep Dive 1: The Midnight Spike, a Peak You Can See Coming (Week 10 Concepts: Capacity Planning; Week 4: Thundering Herd)#

You

"The worst minute of the year is on the calendar months ahead. It's a planning problem with a rehearsal, and a caching problem where nothing may expire at the same moment."

This builds on Week 10, Day 4 · Capacity Planning, Week 4, Day 3 · Thundering Herd and Week 4, Day 5 · Multi-Tier Caching.

Your turn · checkpoint 3not answered

11:59:59

It's one second to midnight. At 12:00:00 millions of open apps will ask for the sale version of the same home page, deal pages and prices. In 2014 Flipkart sized for 20× the traffic and was still overrun. What must be warm before midnight, and how do you know it's enough? What happens if every cached price has a TTL that ends at midnight? And if load still outruns you at 12:00:30, what do you switch off first?

Think it through, or jot it down. Then compare.

One strong answer

  • Size from a forecast, then rehearse it. Forecast per surface (home, search, product, checkout, payments), provision with a margin, and load-test production at that rate weeks before. 2014's lesson: a multiplier isn't a forecast; a new kind of day has its own shape.
  • Warm everything read at midnight: sale pages on the CDN, prices and offers in every cache node, search caches filled by replaying top queries, connection pools open. A cold cache at 12:00 is an outage.
  • Never expire at midnight. If cached prices expire together, every miss hits the price service in the same second. Build the sale's prices as a new version under new keys, push them before midnight, and flip one pointer.
  • Shed by tier. Checkout, payment and order status are tier 1; product pages, search and cart are tier 2 and can serve slightly stale data; recommendations, reviews, personalised widgets and order history are tier 3 and go first.

What is published: in 2014 Flipkart "had deployed nearly 5000 servers and had prepared for 20 times the traffic growth", and traffic was still much higher. A Flipkart architect and Google Cloud wrote in 2023 that the sale brings "a transaction volume surge of six to eight times" business-as-usual, and that its data platform ingests about 60 TB of compressed messages a day normally and 500 TB a day at peak events. Aerospike reported in November 2024 that Flipkart's platform sustained 95 million transactions a second during The Big Billion Days, run on Kubernetes. A CNCF case study (2026) describes Flipkart's central chaos platform running 2,000+ experiments, finding 6+ critical misconfigurations ahead of TBBD 2024 and cutting the time to detect infrastructure failures by about 20% and to resolve them by about 25%.

  • Forecast per surface, not per site. Pages multiply by the opening's concurrency; payments by far more (1,000× in Flipkart's words). Size each tier by its own multiplier, and give the banks and gateways the forecast too.
  • Stateful tiers first. Databases and caches must rebalance data when they grow; grow them days ahead, then freeze.
  • Rehearse failure, not just load. A load test proves capacity; a chaos test proves the fallbacks: kill a cache node or slow a bank at the forecast rate and check that shedding and breakers behave.
  • Freeze changes. No deploys or config pushes in the sale window except fixes; every risky feature behind a flag.
  • The app helps. It downloads the sale's assets hours ahead, counts down on the server's clock, and adds 0–3 s of random jitter to its first refresh after midnight, so 6 million phones don't ask in the same 100 ms.
DiagramThe midnight switch: build ahead, push ahead, flip once
%% title: The midnight switch: build ahead, push ahead, flip once
flowchart TD
  STAGE["Sale ops stage version v42<br/>prices, offers, deal states"]:::service
  BUILD["Build and validate v42<br/>guardrails, two approvals"]:::core
  PUSH@{ icon: "sd:cache", label: "Every cache node<br/>v41 live, v42 loaded" }
  EDGE@{ icon: "sd:cdn", label: "CDN<br/>v42 pages pushed, not live" }
  FLIP["12:00:00 · pointer v41 to v42<br/>one write, read everywhere"]:::hot
  APP@{ icon: "sd:phone", label: "Apps<br/>refresh with 0 to 3 s jitter" }
  STAGE ==> BUILD ==> PUSH
  BUILD ==> EDGE
  PUSH ==> FLIP
  EDGE ==> FLIP
  FLIP ==> APP
  class PUSH cache
  class EDGE edge
  class APP client
pythonstage · 30 lines
# sale/switch.py

"""
Switching a sale phase on by version (illustrative). Every price, offer and deal
state for the phase is written under a new version before it starts; the start
is one pointer write. Readers resolve the pointer, then read version-keyed
entries, so nothing expires or is rewritten at midnight.
"""

import time

ACTIVE = "sale:active_version"


def stage(kv, version: str, prices: dict, offers: dict, deals: dict) -> None:
    for sku, p in prices.items():
        kv.set(f"price:{version}:{sku}", p)            # new keys; the live version is untouched
    for sku, o in offers.items():
        kv.set(f"offer:{version}:{sku}", o)
    for deal_id, d in deals.items():
        kv.set(f"deal:{version}:{deal_id}", d)
    kv.set(f"ready:{version}", True)                   # set only after validation passes


def activate(kv, version: str, at_epoch: float) -> None:
    assert kv.get(f"ready:{version}"), "never flip to a version that isn't fully staged"
    while time.time() < at_epoch:
        time.sleep(0.01)
    kv.set(ACTIVE, version)                            # the whole switch is this one write

Deep Dive 2: Flash Deals and the Waiting Line (Week 3 Concepts: Backpressure and Flow Control; Week 1: Rate Limiting)#

You

"A deal is a tiny amount of stock with an enormous queue in front of it. The store must stay open for everyone browsing, so the queue belongs to the deal, not the site. And the decision who gets in must be fast, fair across networks, and cheap to say no to."

This builds on Week 3, Day 3 · Backpressure and Flow Control, Week 1, Day 3 · Rate Limiting at Scale and Week 9, Day 5 · Security Architecture.

Your turn · checkpoint 4not answered

5,000 phones, 300,000 thumbs

At 12:00:00 the deal goes live. In the first ten seconds 300,000 people tap "Buy now"; a third of them are on slow mobile networks in small towns, and a few thousand of the taps come from scripts. If the server takes whoever arrives first, who wins? How many people should you let into checkout, given that some who get in won't pay? What do the other 290,000 see, and when? And how do you stop one reseller with 200 accounts taking 200 phones?

Think it through, or jot it down. Then compare.

One strong answer

  • First-come at the server favours the fastest network, not the first thumb. Fibre in a metro beats 4G in a small town by hundreds of milliseconds. Treat every tap in the first couple of seconds as simultaneous and order them randomly, then first-come after that.
  • Admit a little more than the stock. Issue tokens for units × 1.3 (tuned from past drop-off), each valid for 3 minutes. A token is the only way to reach the stock.
  • Everyone else gets an answer from the edge within seconds: "You're in line, position 12,400" while tokens may come back, then "Sold out". Not a spinner, not an error page.
  • Limits and bots before tokens: one token per customer, per device and per delivery address for the deal; device and behaviour checks at the gateway; tokens are signed and bound to the account, so they can't be shared or replayed.

What is published: Flipkart hasn't published how it runs deal queues. On record: in 2014 Flipkart "ran out of the stock for many products within a few minutes (and in some cases, seconds) of the sale going live"; a month earlier, a flash sale on Flipkart sold 40,000 phones in 4.2 seconds to a pool of 250,000+ registered buyers (Xiaomi, reported by GSMArena, September 2014). In 2026 Flipkart reported that nearly one in two opening-hour visits came from Tier 3 towns and beyond, and about 60% of demand from outside the metros, which is why "fastest network wins" isn't a neutral rule.

Three ways to sell 5,000 units to 300,000 people

First-come at the database

  • Every tap is a conditional write on the stock key
  • 30,000 writes a second on one row; ~98% fail
  • The fastest networks and the bots win
  • A slow stock store slows the whole store

A waiting room for the whole site

  • Right when everything on sale is the scarce thing (a concert)
  • Here it blocks 6 million people browsing to protect one page

A line per deal, tokens at the edge

  • Only the deal's "Buy now" queues; the rest of the store is untouched
  • Tokens ≈ units × 1.3; only token holders reach stock
  • Lottery window for the first seconds, limits per customer, an honest answer for everyone else
  • The line lives at the edge. Per deal: a counter of tokens issued and a set of holders, in memory with a replica. Issuing is one atomic increment-if-below-limit; refusing is a read.
  • Tokens recycle. A token that expires unused, or whose payment fails, admits the next person in line, until the units are sold.
  • "Sold out" is a state, published once on the CDN when tokens and holds are exhausted; later taps never reach the service.
  • Bots are an economics problem. Per-customer, device and address limits make each extra phone cost a new verified account and address; signed, account-bound tokens can't be shared; risky sessions get a challenge before a token, never during payment.
  • This is not the BookMyShow waiting room. BookMyShow (Bonus Problem 16) queues a whole on-sale because every seat is the scarce thing. Here the scarce thing is one product among millions, so the line wraps one button.
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 deal 4471<br/>customer, device, address"]:::service
  BOT{"Limits and bot checks pass?"}
  WIN{"Inside the lottery window?"}
  LOT["Random order among<br/>taps in the first 2 s"]:::service
  TOK{"Tokens issued below<br/>units × 1.3?"}
  GIVE@{ icon: "sd:ticket", label: "Signed token<br/>3 minutes, one per customer" }
  WAIT["You're in line<br/>position shown"]:::service
  SOLD@{ icon: "sd:cdn", label: "Sold out<br/>published on the CDN" }
  TAP ==> BOT
  BOT -->|"no"| WAIT
  BOT ==>|"yes"| WIN
  WIN -->|"yes"| LOT ==> TOK
  WIN -->|"no"| TOK
  TOK ==>|"yes"| GIVE
  TOK -->|"no, tokens may return"| WAIT
  WAIT -.->|"all tokens used"| SOLD
  class GIVE ok
  class SOLD edge
pythonissue · 34 lines
# deals/admission.py

"""
Admission tokens for a limited deal (illustrative). Taps in the first seconds
are ordered randomly, then first come first served; tokens are capped a little
above the deal's units and recycled when they expire or their payment fails.
"""

import hmac, hashlib, random, time

WINDOW_S = 2.0            # erase network-latency advantage in the first seconds
OVERBOOK = 1.3            # tokens per unit, tuned from past drop-off
TOKEN_TTL_S = 180


def issue(kv, deal: dict, customer: str, now: float, secret: bytes):
    if kv.sismember(f"holders:{deal['id']}", customer):
        return {"status": "ALREADY_IN"}                      # one per customer
    if now < deal["starts_at"] + WINDOW_S:
        kv.zadd(f"window:{deal['id']}", {customer: random.random()})
        return {"status": "IN_LINE"}                         # drawn in random order when it closes
    return _try_admit(kv, deal, customer, now, secret)


def _try_admit(kv, deal, customer, now, secret):
    cap = int(deal["units"] * OVERBOOK) + kv.get_int(f"returned:{deal['id']}")
    if not kv.incr_if_below(f"issued:{deal['id']}", cap):    # one atomic step
        kv.rpush(f"line:{deal['id']}", customer)
        return {"status": "IN_LINE"}
    kv.sadd(f"holders:{deal['id']}", customer)
    expires = now + TOKEN_TTL_S
    msg = f"{deal['id']}|{customer}|{expires}".encode()
    sig = hmac.new(secret, msg, hashlib.sha256).hexdigest()  # bound to this account
    return {"status": "ADMITTED", "token": f"{msg.decode()}|{sig}", "expires": expires}
Quick check3 questions · +5 XP each

The spike and the deal line

  1. Q1/3 Why build the sale's prices as a new version instead of letting cached prices expire at midnight?

    Answer & explanation

    Answer: B

    The thundering herd is caused by synchronised expiry. Pre-load under new keys, then switch.

  2. Q2/3 A buyer on fibre in a metro and a buyer on 4G in a small town tap at the same instant. How does the deal line keep it fair?

    Answer & explanation

    Answer: B

    A short lottery window erases a few hundred milliseconds of network advantage.

  3. Q3/3 A 5,000-unit deal issues 6,500 tokens. Can it oversell?

    Answer & explanation

    Answer: B

    Tokens bound the load on inventory; inventory bounds the sales.

Deep Dive 3: Holding Stock Without Overselling (Week 5 Concepts: Consistency Models; Week 1: Hot Keys; Week 2: Idempotency)#

You

"The line in the 2014 email that matters most to me is the cancellations: orders accepted for items that had sold out seconds earlier. That's what happens when an order is accepted from a number that was true a few seconds ago. The page can be a few seconds behind. The step that takes a unit must not be."

This builds on Week 5, Day 1 · Consistency Models, Week 1, Day 4 · Hot Keys and Skew and Week 2, Day 2 · Idempotency in Practice.

Your turn · checkpoint 5not answered

Sold out seconds earlier

Rohan's phone page says "In stock". 6,500 token holders head for checkout at once for 5,000 units. When should a unit be taken: when he adds it to the cart, when checkout starts, or when payment succeeds? How long can you hold it if he doesn't pay? His UPI request is still pending when the hold is about to expire: what happens? And the 5,000 units sit in three warehouses: does that change anything?

Think it through, or jot it down. Then compare.

One strong answer

  • Take it when checkout starts. At add-to-cart, millions of carts would lock stock nobody buys. At payment, two people could pay for the last unit and one gets a cancellation: the 2014 symptom.
  • A hold is a conditional decrement plus a record with an expiry, keyed by the checkout id: "take 1 if available ≥ 1; record hold k1, expires 12:10:01". A retry with the same key finds the same hold.
  • Holds expire, unless money is in flight. If payment hasn't started, the hold lapses at 10 minutes and the unit goes back. If a payment is pending or its outcome is unknown, the hold is extended up to a cap (say 30 minutes) while payments resolves it; then it's either sold or released, never both.
  • Stock is per warehouse. The hold picks a warehouse that has the unit and can meet the delivery promise; three warehouses mean three counters, naturally sharded.

What is published: the 2014 email explained cancellations as many people buying the same product at once, so orders were accepted for items that had sold out seconds earlier, and the founders promised to "minimise any cancellations". Flipkart's engineers list inventory and pricing among the use cases on its Aerospike platform (Aerospike's write-up of their talk, 2025), and PingCAP describes Flipkart adopting TiDB, a distributed SQL database, after its MySQL setup hit limits on failover, replication and scale (2025). How Flipkart reserves stock, when, and for how long is not published; the rest is our design.

DiagramOne unit of deal stock, from available to sold or back
%% title: One unit of deal stock, from available to sold or back
stateDiagram-v2
  [*] --> AVAILABLE: received at a fulfilment centre
  AVAILABLE --> HELD: checkout starts, key k1
  HELD --> AVAILABLE: hold expired, no payment
  HELD --> PAYING: payment started
  PAYING --> SOLD: payment succeeded
  PAYING --> AVAILABLE: payment failed
  PAYING --> PAYING: outcome unknown, hold extended
  SOLD --> [*]
  class SOLD ok
  class PAYING queue
  • Three numbers, one invariant. Per SKU per warehouse: available + held + sold = on hand. Holds, sales and expiries move units between them; a reconciler checks the sum every few minutes.
  • Deal stock is its own pool, carved out before the sale under the deal's own counters, so a deal can't drain the regular listing.
  • The hot key is bounded by the line. With admission in front, a deal sees ~6,500 holds in total, not 30,000 writes a second. If a non-deal product is still hot, split its stock into shards (per warehouse, then sub-shards), each decremented only while positive, so the shards can't add up to more than exist. Dream11 (Bonus Problem 25) does the same with mega-contest spots.
  • Strong where it counts. Counter and hold record are one atomic write on a strongly consistent store; pages read replicas and caches. A page that says "In stock" a second too long costs a "sorry, just sold out" at checkout, not a cancelled order.
  • Expiry is a sweep, not a timer per hold. Holds are indexed by expiry; a sweeper releases expired ones every second. A payment that succeeds after its hold was released takes another unit if one is free, or is refunded.
pythonOutOfStock · 50 lines
# inventory/hold.py

"""
Holding one unit at checkout (illustrative). The decrement and the hold record
are one atomic step on the stock store, keyed by the checkout id so a retry
finds the same hold. Holds expire unless a payment is in flight; a sweeper
returns expired units. available + held + sold always equals on hand.
"""

HOLD_S = 600              # 10 minutes to start paying
MAX_HOLD_S = 1800         # cap while a payment's outcome is unknown


class OutOfStock(Exception):
    pass


def hold(store, sku: str, fc: str, qty: int, key: str, now: float) -> dict:
    existing = store.get(f"hold:{key}")
    if existing:
        return existing                                      # a retry: same hold
    ok = store.transact([
        store.decrement_if_at_least(f"stock:{sku}:{fc}:available", qty),
        store.increment(f"stock:{sku}:{fc}:held", qty),
        store.put_if_absent(f"hold:{key}", {"sku": sku, "fc": fc, "qty": qty,
                                            "state": "HELD", "expires": now + HOLD_S}),
        store.index_add("holds_by_expiry", key, score=now + HOLD_S),
    ])
    if not ok:
        raise OutOfStock(sku)
    return store.get(f"hold:{key}")


def extend_for_payment(store, key: str, now: float, started: float) -> None:
    h = store.get(f"hold:{key}")
    new_exp = min(now + 300, started + MAX_HOLD_S)           # money in flight: wait, within a cap
    store.compare_and_set(f"hold:{key}", expect_state=h["state"], state="PAYING", expires=new_exp)
    store.index_add("holds_by_expiry", key, score=new_exp)


def sweep(store, now: float) -> None:
    for key in store.index_range("holds_by_expiry", max_score=now):
        h = store.get(f"hold:{key}")
        if h and h["state"] in ("HELD", "PAYING"):
            store.transact([
                store.compare_and_set(f"hold:{key}", expect_state=h["state"], state="RELEASED"),
                store.increment(f"stock:{h['sku']}:{h['fc']}:available", h["qty"]),
                store.increment(f"stock:{h['sku']}:{h['fc']}:held", -h["qty"]),
            ])
        store.index_remove("holds_by_expiry", key)

Deep Dive 4: Checkout and the Payment Surge (Week 2 Concepts: Idempotency, Circuit Breakers; Week 3: Transactional Outbox)#

You

"Checkout is where our system meets systems we don't run: gateways, UPI, banks' authentication pages, OTP over SMS. At a thousand times their normal load, some will be slow and some answers won't come at all. The order must stay correct when a partner is slow or silent."

This builds on Week 2, Day 2 · Idempotency in Practice, Week 2, Day 3 · Circuit Breakers, Week 2, Day 1 · Timeout Hell and Week 3, Day 2 · Transactional Outbox.

Your turn · checkpoint 6not answered

12:00:20, one bank slows down

Rohan pays by UPI. At the same moment, one large bank's card authentication slows from 2 seconds to 20. Rohan's UPI app shows "pending" and he taps "Pay" again. Ninety seconds later the gateway still hasn't said whether the first payment succeeded. How do you keep the order from being charged twice, cancelled while money is in flight, or lost between "paid" and the warehouse? And what do you do about the slow bank?

Think it through, or jot it down. Then compare.

One strong answer

  • One key for one attempt. Checkout creates the order in PENDING_PAYMENT with an idempotency key; every call to payments carries it. The second tap finds the first attempt in flight and shows its status instead of starting another charge.
  • Unknown is a state, not a failure. The order goes to PAYMENT_UNKNOWN, the stock hold is extended, and a reconciler polls the gateway's status API until it gets a definite answer; only then is the order confirmed or failed.
  • Paid and "tell the warehouse" commit together. The order's state change and an outbox row are written in one transaction; a relay publishes the outbox to the message bus. A crash between them can't lose an order.
  • Route around the slow bank. Per-bank success rate and latency drive a circuit breaker: checkout stops offering that bank's cards first, suggests UPI or another method, and sheds new attempts instead of queuing them behind 20-second timeouts.

What is published: Gaurav Arora, Flipkart's vice-president of payments and SuperCoins, told Business Standard in September 2026 that Flipkart handles nearly 1,000 times more payment requests during peak festive periods than under normal business conditions, and that biometric-authenticated transactions recorded a 12–15% higher success rate than OTP-based ones in the payment flows discussed (as reported by DQIndia, which adds that a surge of that size can't be met by expanding Flipkart's own databases alone, since payments also run through banks, gateways, authentication services and telecom networks). Flipkart has open-sourced Phantom, a proxy for calling distributed services "inspired by Twitter Finagle clients" and built on Netflix Hystrix, used for "several hundred million API calls"; Varadhi, its REST message bus, in production for about ten years "sending billions of messages daily"; and a Kafka client with priority levels, where each level gets reserved consuming capacity (2018).

DiagramA double tap and an unknown outcome, resolved without a double charge
%% title: A double tap and an unknown outcome, resolved without a double charge
sequenceDiagram
  autonumber
  participant A as Rohan's 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 order o77, key k1
  P->>G: UPI collect for o77
  G-->>P: No answer in 30 s
  P->>C: o77 PAYMENT_UNKNOWN
  C->>I: Extend hold k1 while money is in flight
  A->>C: Pay again, key k1
  C-->>A: Payment in progress, no new charge
  R->>G: Status of o77?
  G-->>R: Success
  R->>C: o77 paid
  C->>C: CONFIRMED and outbox row, one transaction
  C->>I: Hold k1 becomes a sale
  • The order is a state machine. PENDING_PAYMENT → PAYMENT_UNKNOWN or PAYMENT_FAILED or CONFIRMED → PACKED → SHIPPED → DELIVERED; each transition is a compare-and-set on the order's version, so two workers can't both confirm it.
  • A timeout is unknown, not failed. It's never retried as a new charge; it's resolved by status polling, then by the gateway's settlement file the next day.
  • Steer to the method that works. Order payment options by live success rate. A method with a 12–15% higher success rate, as Flipkart's Gaurav Arora reported for biometric authentication, is capacity: every failure is a retry that comes back.
  • Bulkheads per partner. Each bank and gateway gets its own pool and concurrency limit, the Hystrix pattern Phantom was built on; a slow bank fails fast in its own pool and can't starve the others.
  • Priority on the bus. Order confirmations, payment results and stock releases ride a high-priority lane; analytics and emails a low one, which waits when consumers fall behind.
pythonpay · 43 lines
# checkout/pay.py

"""
Paying for an order (illustrative). A second tap is a lookup by key; an
unanswered call is UNKNOWN, never retried as a new charge; a confirmed order
and its outbox event commit in one transaction.
"""

GATEWAY_TIMEOUT_S = 30


def pay(db, payments, inventory, order_id: str, key: str, method: str) -> str:
    attempt = db.get_payment_attempt(key)
    if attempt:
        return attempt.state                                 # second tap: show the first attempt
    db.insert_payment_attempt(key, order_id, method, state="STARTED")
    inventory.extend_for_payment(key)
    try:
        result = payments.charge(order_id, key=key, method=method, timeout_s=GATEWAY_TIMEOUT_S)
    except Timeout:
        db.set_attempt_state(key, "UNKNOWN")
        db.transition(order_id, "PENDING_PAYMENT", "PAYMENT_UNKNOWN")
        return "UNKNOWN"                                     # the reconciler takes it from here
    return settle(db, inventory, order_id, key, result.status)


def settle(db, inventory, order_id: str, key: str, status: str) -> str:
    with db.tx():
        if status == "SUCCESS":
            db.transition_any(order_id, ["PENDING_PAYMENT", "PAYMENT_UNKNOWN"], "CONFIRMED")
            db.outbox_add("orders.confirmed", {"order": order_id}, priority=0)   # same transaction
        else:
            db.transition_any(order_id, ["PENDING_PAYMENT", "PAYMENT_UNKNOWN"], "PAYMENT_FAILED")
        db.set_attempt_state(key, status)
    if status == "SUCCESS":
        inventory.mark_sold(key)
    else:
        inventory.release(key)
    return status


class Timeout(Exception):
    pass
Quick check2 questions · +5 XP each

Stock holds and payments

  1. Q1/2 A UPI payment is pending when the 10-minute hold is about to expire. What happens?

    Answer & explanation

    Answer: B

    Money in flight extends the hold. Unknown outcomes get resolved, not guessed.

  2. Q2/2 The gateway doesn't answer for 30 seconds. Should checkout retry the charge?

    Answer & explanation

    Answer: C

    A timed-out charge may have succeeded. Retrying as new risks a double charge; failing it risks taking money for nothing.

Deep Dive 5: Prices, Catalog and Search Under Load (Week 4 Concepts: Cache Invalidation, Multi-Tier Caching; Week 7: Search)#

You

"In 2014 some prices reverted to full rates for a few hours. Flipkart didn't say why, and I won't guess. But the class of bug is familiar: a price assembled from several sources and cached in several places, one of which falls back to a default. So two rules: one version of the truth at a time, and when in doubt, don't sell."

This builds on Week 4, Day 2 · Cache Invalidation, Week 4, Day 5 · Multi-Tier Caching and Week 7, Day 1 · Search Fundamentals.

Your turn · checkpoint 7not answered

The price that went back up

At midnight, 5 million products change price and thousands of bank offers start. Search, the product page, the cart and checkout each show a price. One cache node restarts at 12:03 empty; another loses the offer service. What should each page show, and what must checkout charge? Should the search index be rebuilt with the new prices? And what if a sale price was entered as ₹999 instead of ₹9,999?

Think it through, or jot it down. Then compare.

One strong answer

  • One versioned price book. Prices and offers for the phase are a staged version (Deep Dive 1). Every page reads the active version's entries; an empty cache node refills from that version, never from "the latest row" or a default.
  • Fail closed. If a price or offer can't be read, show "price unavailable, try again" and no Buy button, never the list price or a stale offer. Hiding a button for 30 seconds costs little; a wrong price costs trust.
  • Checkout re-prices from the source with the active version, and charges the price shown in the cart or lower; if the price went up since the cart (an offer ended), the customer sees the new total before paying.
  • Don't rebuild the index for prices. The index holds what changes slowly (titles, attributes, ratings); price, offer and stock badges are joined at serve time from the price cache.
  • Guardrails before the flip. A staged price more than, say, 70% below the product's 90-day low, or below the seller's cost, needs a second approval; the build fails otherwise. And every deal has a kill switch that pulls it from sale in seconds.

What is published: the 2014 email: some prices changed to non-discounted rates for a few hours, and "we realise that this breaks the trust our customers have put in us". Flipkart's engineers list search, recommendations, ads, pricing and inventory among the use cases on its Aerospike platform, which Aerospike's write-up of their talk says serves "consistent sub-millisecond reads, even during Big Billion Days peaks" (2025). Its catalogue is 150 million products across 80+ categories (Flipkart, 2024; Google Cloud, 2023). How Flipkart versions prices or builds its search index for the sale isn't published.

DiagramWhere a price comes from, on every page
%% title: Where a price comes from, on every page
flowchart TD
  PTR["Active version pointer<br/>v42 since 12:00:00"]:::hot
  BOOK@{ icon: "sd:cache", label: "Price book v42<br/>price, offers, deal state" }
  IDX@{ icon: "sd:index", label: "Search index<br/>titles, attributes, ratings" }
  SERP["Search results<br/>index hits plus prices joined"]:::service
  PDP["Product page<br/>price, offers, stock badge"]:::service
  CHK["Checkout<br/>re-price from v42, charge shown or lower"]:::core
  MISS["Price unknown<br/>no Buy button, retry"]:::bad
  PTR ==> BOOK
  IDX ==> SERP
  BOOK ==> SERP
  BOOK ==> PDP
  BOOK ==> CHK
  BOOK -.->|"miss or offers down"| MISS
  class BOOK cache
  class IDX store
  • Search splits slow and fast fields. Matching and ranking use the index, refreshed in minutes; price, offer, stock and delivery date come from caches for the top results only.
  • Cache the head. A few thousand queries ("smart tv", "washing machine") carry much of the sale's search traffic; their first page of result ids is cached for a minute.
  • Degrade relevance, not results. Under load, drop personalised re-ranking first and cap result depth.
  • Three tiers of cache. App, edge and in-memory stores in each data centre, all keyed by version: a version change makes every key new at every tier, without a purge.
  • Offers are part of the version, so the discount on the page and at payment come from the same book and can't disagree.
pythonPriceUnavailable · 35 lines
# pricing/quote.py

"""
Quoting a price for a page or for checkout (illustrative). Every read goes
through the active version; a missing entry or an unreachable offer service
fails closed (no Buy button), never falls back to list price. Checkout
re-prices and charges the cart's price or lower.
"""

from decimal import Decimal


class PriceUnavailable(Exception):
    pass


def quote(cache, sku: str, card_bin: str | None = None) -> dict:
    v = cache.active_version()
    entry = cache.get(f"price:{v}:{sku}")
    if entry is None:
        raise PriceUnavailable(sku)                      # fail closed
    price = Decimal(entry["price"])
    offer = None
    if card_bin:
        offers = cache.get(f"offer:{v}:{sku}")
        if offers is None:
            raise PriceUnavailable(sku)                  # don't guess the offer either
        offer = best_offer(offers, card_bin, price)      # eligibility by card range, capped
    final = price - (offer["discount"] if offer else 0)
    return {"version": v, "price": price, "offer": offer, "final": final}


def checkout_price(cache, cart_line: dict, card_bin: str | None) -> Decimal:
    now = quote(cache, cart_line["sku"], card_bin)
    return min(now["final"], Decimal(cart_line["shown_final"]))   # never more than shown

Deep Dive 6: Delivery Promises That Survive the Surge (Week 10 Concepts: Capacity Planning; Week 3: Backpressure)#

You

"A delivery date is a promise about capacity: someone has to pick, pack, sort and drive this parcel by then. If the sale fills tonight's warehouse waves, the date on the page has to move before the customer orders, not after."

This builds on Week 10, Day 4 · Capacity Planning, Week 3, Day 3 · Backpressure and Flow Control and Week 10, Day 1 · SLIs, SLOs and SLAs.

Your turn · checkpoint 8not answered

Delivery by Tue, 13 Oct

Rohan's washing machine sits in a fulfilment centre near Kolkata that works in waves: the 12:30 wave can process 20,000 units, picking and packing take 60 minutes, and the truck towards Bhagalpur leaves at 13:30. At midnight three times the usual orders arrive. What happens to orders that don't fit in the 12:30 wave? Should the page's date change, and when? And if Bhagalpur orders and orders for a 20:30 truck compete for the 12:30 wave, who should get it?

Think it through, or jot it down. Then compare.

One strong answer

  • The wave is a stock of capacity. Each order reserves a unit of capacity in a wave at checkout, the same way it holds a unit of product. When the 12:30 wave is full, new orders "spill" to the next wave, miss the 13:30 truck and lose a day.
  • The page's date follows the capacity. The promise table for each (fulfilment centre, pincode) is recomputed as waves fill: once the 12:30 wave is nearly full, the page starts showing 14 October to new viewers. Orders already placed keep 13 October, because their capacity is reserved.
  • Reserve for the urgent truck. Don't fill the wave first-in-first-out. Keep part of the 12:30 wave for orders whose truck leaves at 13:30; orders for the 20:30 truck can wait for a later wave without losing anything.
  • Plan capacity before the sale. Demand is forecast per centre and pincode weeks ahead and people, shifts and trucks planned from it; during the sale the engine allocates what was planned.

What is published: Flipkart's engineers wrote in 2026 that a Promise Engine "calculates the precise delivery date shown to a customer during checkout" from "available processing capacity across network and truck connection schedules". Fulfilment centres work in waves, each with a cut-off and a capacity (their example: a 12:30 cut-off with 20,000 units, and pick-to-dispatch times of 30 to 180 minutes), with operational constraints limiting a centre to 6–8 cut-offs a day. When a wave is full, orders "spill" to the next, and spilled orders "miss the truck and likely wait 24 hours for the next one". The engine typically allocates capacity first-in-first-out; Flipkart built a way to reserve capacity for orders with impending truck connections and, during sales such as BBD, when "demand massively exceeds capacity", for high-priority cohorts. Its Central Planning Platform forecasts demand down to pincode level (Flipkart Tech, 2026). Ahead of the 2026 sale, Flipkart said automation in Ekart's warehouses raised peak processing capacity by over 30% and that it added over 900 new delivery hubs (Flipkart, October 2026).

DiagramAn order takes a slot in a wave, or the date moves
%% title: An order takes a slot in a wave, or the date moves
flowchart TD
  ORD["Order line<br/>washing machine to 812001"]:::service
  FCS@{ icon: "sd:warehouse", label: "Fulfilment centre<br/>holds the unit" }
  WAVE{"12:30 wave has capacity<br/>for this truck?"}
  RES["Reserve 1 unit of capacity<br/>urgent-truck share first"]:::hot
  TRUCK@{ icon: "sd:truck", label: "13:30 truck<br/>towards Bhagalpur" }
  SPILL["Next wave<br/>misses the truck, plus 1 day"]:::bad
  TABLE@{ icon: "sd:cache", label: "Promise table<br/>FC × pincode, recomputed" }
  ORD ==> FCS ==> WAVE
  WAVE ==>|"yes"| RES ==> TRUCK
  WAVE -->|"no"| SPILL
  RES -.->|"capacity left"| TABLE
  SPILL -.->|"new date for new viewers"| TABLE
  class FCS store
  class TRUCK external
  class TABLE cache
  • Two promises, like two stocks. The page shows a cached date per (fulfilment centre, pincode), recomputed as capacity burns down; checkout reserves the slot. The order's date is the slot's date, not the page's.
  • Promise = processing + transit. Transit is mostly fixed by truck schedules; processing is the variable, at each touch point (fulfilment centre, mother hub, delivery hub).
  • Hold the slot with the stock. Both are made at checkout and released together if payment fails.
  • Choose the warehouse by promise: the centre whose next feasible slot gives the earliest date, not the nearest one with a full wave.
  • Under a surge, protect the cohort, show the truth to the rest. Reserve capacity for chosen cohorts and let other dates move out honestly: a later date before the order beats a broken one after it.
pythonWave · 43 lines
# promise/allocate.py

"""
Reserving capacity for an order (illustrative). Each fulfilment-centre wave has
a capacity and a cut-off; part of it is reserved for orders whose truck leaves
soon after the cut-off. If no feasible wave catches the truck, the promise
moves a day. The order keeps the date of the slot it reserved.
"""

from dataclasses import dataclass


@dataclass
class Wave:
    cutoff: int               # minutes after midnight
    capacity: int
    urgent_share: float       # part kept for orders whose truck leaves soon after the cut-off
    used_urgent: int = 0
    used_general: int = 0


def take(w: Wave, urgent: bool) -> bool:
    urgent_cap = int(w.capacity * w.urgent_share)
    if urgent and w.used_urgent < urgent_cap:
        w.used_urgent += 1
        return True
    if w.used_general < w.capacity - urgent_cap:      # urgent orders may also use the general share
        w.used_general += 1
        return True
    return False


def reserve(waves: list, now: int, p2d: int, truck_departs: int, transit_days: int, today: int) -> dict:
    for w in sorted(waves, key=lambda w: w.cutoff):
        if now + p2d > w.cutoff:
            continue                                  # can't be picked and packed by this cut-off
        catches_truck = w.cutoff <= truck_departs
        urgent = catches_truck and truck_departs - w.cutoff <= 120
        if not take(w, urgent):
            continue                                  # full: spill to the next wave
        ship_day = today if catches_truck else today + 1
        return {"wave": w.cutoff, "deliver_by_day": ship_day + transit_days}
    return {"wave": "tomorrow", "deliver_by_day": today + 1 + transit_days}
Quick check3 questions · +5 XP each

Prices, search and promises

  1. Q1/3 A cache node restarts empty and can't reach the offer service. What should the product page show?

    Answer & explanation

    Answer: C

    Fail closed. A missing button for seconds is cheaper than a wrong price for hours.

  2. Q2/3 The 12:30 wave is full. What happens to the date shown to a new visitor from Bhagalpur?

    Answer & explanation

    Answer: B

    The page's date follows remaining capacity; orders already placed keep the slots they reserved.

  3. Q3/3 Flipkart's engine typically allocates wave capacity first-in-first-out. Why reserve part of a wave for orders whose truck leaves soon?

    Answer & explanation

    Answer: B

    Give the scarce early slot to the orders it actually speeds up.

Phase 5 · Stage 5 of 7 · 5 min budget · 2 min read

Phase 5: Scaling and Edge Cases (5 minutes)#

Interviewer

"Walk me through the opening night from the platform's side, then the cases that bite."

Rohan's Night, From the Platform's Side#

The Early Access opening (the midnight start for Plus and Black members is Flipkart's; the rest is illustrative)
  • Weeks before · Forecast and rehearse
    • Demand forecast per surface, warehouse and pincode; stateful clusters grown; load and chaos tests at the forecast
  • Days before · Stage the sale
    • Price book v42 built, checked and loaded everywhere; deal stock carved out; code freeze
  • 11:00 pm · Warm up
    • Sale pages on the CDN; search caches filled; apps download the sale's assets
  • 12:00:00 am · Flip
    • The active version moves to v42; apps refresh with jitter; Early Access opens to Plus and Black members
  • 12:00:02 · Deals open
    • The lottery window closes; tokens are issued
  • 12:00 to 12:10 · Peak
    • Orders reach ~1,200 a second; tier-3 widgets are shed; one slow bank's card flow is demoted behind UPI
  • 12:10 · Holds expire
    • Unpaid holds return; returned tokens admit the next people in line; deals flip to sold out on the CDN
  • Morning · Settle and replan
    • Unknown payments resolved from gateway files; the night's orders feed the next day's warehouse plan

Edge Cases#

Edge cases
Edge case 1: A deal priced ₹999 instead of ₹9,999

Problem: A typo in the staged price book makes a phone 90% cheaper than intended; at midnight it sells in seconds.

Solution: Build-time guardrails (Deep Dive 5), a per-deal kill switch that pauses it on the CDN in seconds, and a policy for orders already placed, decided before the sale, not at 12:05.

Edge case 2: A deal's stock count is wrong

Problem: A warehouse count finds 300 of a deal's 5,000 units missing after the deal opened.

Solution: Lower the deal's available count at once (never below what's held or sold); if holds exceed what's really there, the latest holds are released before payment with an apology, which is better than cancelling paid orders later.

Edge case 3: A fulfilment centre goes dark

Problem: A power failure stops one centre for six hours during the sale.

Solution: Set its wave capacity to zero, so new orders go to other centres or see a later date; re-plan its promised orders and tell customers whose date moves.

Quick check2 questions · +5 XP each

Scaling and edge cases

  1. Q1/2 A staged deal price is 90% below the product's 90-day low. What should happen?

    Answer & explanation

    Answer: B

    Guardrails before the flip, a kill switch after it, and a policy decided in advance.

  2. Q2/2 A fulfilment centre loses power for six hours mid-sale. What does the promise engine do?

    Answer & explanation

    Answer: B

    Capacity is data. Set it to what's real and the dates follow.

Phase 6 · Stage 6 of 7 · 1 min

Phase 6: Monitoring and Operations#

You

"The 2014 email is effectively the list of alerts: errors on the site, stock-outs, cancellations, wrong prices. Add the partners and the promises."

Monitoring Dashboard#

Sale-night dashboard — 12:04 am IST, Early Access (illustrative)
Read plane
Metric Now
Concurrent users (forecast) 6.1M (6.5M)
Edge requests / at services 3.0M/s / 610k/s
Product page p99 at the edge 210 ms
Price lookups failing closed 0.002%
Tier-3 widgets shed 35%
Write plane
  • Orders: 1,140/s; checkout p99: 420 ms
  • Deal 4471: tokens 6,500 / 6,500, held 1,210, sold 3,640, in line 184,000
  • Sold beyond stock: 0; stock invariant mismatches: 0
Partners and promises
  • Payment success: UPI 96.1%, cards 91.8%, one issuer 62% (breaker open, option demoted)
  • Payments UNKNOWN: 3,900 open, oldest 4 min
  • Kolkata centre 12:30 wave: 88% full; page date for 812xxx moved to 14 Oct

SLOs for the Sale#

SLOs for the opening, deals, payments and promises
SLO 1: The store stays up
  • Target: product and search pages p99 < 300 ms and success > 99.9% from 11:55 pm to 1:00 am
SLO 2: Stock is exact
  • Target: zero units sold beyond stock; zero stock-invariant mismatches; zero orders cancelled for lack of stock
SLO 3: Money is exact
  • Target: zero duplicate charges; every UNKNOWN payment resolved within 24 hours; price charged never above price shown
SLO 4: Promises hold
  • Target: ≥ 95% of sale-week orders delivered by the promised date; dates shown on pages within one wave of real capacity
Quick check3 questions · +5 XP each

Reading the dashboard

  1. Q1/3 "Sold beyond stock" is 0, but "orders cancelled for lack of stock" shows 40. What does that mean?

    Answer & explanation

    Answer: B

    If every order has a hold, there's nothing to cancel for lack of stock.

  2. Q2/3 UNKNOWN payments climb from 3,900 to 40,000 while UPI success holds steady. Where do you look?

    Answer & explanation

    Answer: B

    Unknowns grow when status polling falls behind or a gateway stops answering; holds are being extended meanwhile.

  3. Q3/3 A deal shows tokens 6,500 of 6,500 issued, sold 3,640, held 0, and 180,000 people in line. What's wrong?

    Answer & explanation

    Answer: B

    Units are left with people waiting: returned tokens should admit the next in line.

Final challenge · Stage 7 of 7 · 2 min

Final Challenge: Day 1, a Month Before Diwali#

The interviewer leans back. "One more."

Interviewer

"Suppose Day 1 opens to everyone at midnight, and a lot goes wrong at once. Diwali is a month away, so every promised date matters. Tell me what you do."

The Day 1 brief
  • Forecast 10 million concurrent users (assumption), about 1.6× the 6.3 million reported for the 2026 opening hour
  • Hourly deals; the first: 10,000 units at 12:00
  • 12:03: one large issuer's card authentication slows to ~20 s
  • 12:07: 1,200 products live with a 25% bank offer instead of 10%
  • 1:00 am: a northern fulfilment centre down for six hours
Your turn · checkpoint 9not answered

What do you protect, and what do you degrade?

Decide the capacity and caching plan for 10 million, how the hourly deals are admitted, what checkout does about the slow issuer, how you correct the offer without breaking the prices people already saw, and what happens to orders and dates tied to the dark centre.

Think it through, or jot it down. Then compare.

One strong answer

  • Capacity: scale to the 10 million forecast with margin, stateful tiers first, days ahead; stage Day 1's price book as its own version; warm caches; shed tier 3 from the start of the peak.
  • Deals: each hourly deal gets its own line, 13,000 tokens for 10,000 units, carved-out stock and a lottery window.
  • The slow issuer: its breaker opens; checkout demotes its cards behind UPI; in-flight charges become UNKNOWN and are reconciled, with holds extended.
  • The wrong offer: build v43 with the corrected offer and flip to it; orders already placed keep the price they were shown; carts priced under v42 are re-quoted, and the customer sees the new total before paying.
  • The dark centre: set its wave capacity to zero; new orders route elsewhere or see later dates; customers whose date moves are told now, not on the old date.
Quick check2 questions · +5 XP each

Final challenge · Quest check

  1. Q1/2 1,200 products went live with the wrong bank offer. How do you fix it?

    Answer & explanation

    Answer: B

    Versions make corrections as clean as launches; "never charge more than shown" protects customers mid-checkout.

  2. Q2/2 A 10,000-unit deal opens on Day 1. How many tokens, and why more than the units?

    Answer & explanation

    Answer: B

    A little overbooking keeps units from going unsold; the stock hold still prevents overselling.

Wrap-up · 8 min

Interview Conclusion#

Transcript
Interviewer

"Why not just lock the product row and let the database decide who gets the deal?"

You

"Because 30,000 people a second would queue on one row to be told no 98% of the time, and the fastest networks would win. The database should decide only among people who can actually succeed. The deal line answers the rest at the edge, fairly, in under two seconds."

Interviewer

"And why not put the whole site behind a waiting room for the first ten minutes?"

You

"Because the scarce thing is a few deals, not the store. Six million people are browsing 150 million products; queuing all of them to protect a few product pages turns a sale into an outage. Queue the deal, cache the store."

Interviewer

"The single most important decision?"

You

"Splitting every promise into a cached hint and a reserved commitment. The page can show 'In stock', ₹9,999 and 'Delivery by 13 Oct' from caches a few seconds old, because checkout holds the unit, re-prices and reserves the slot before taking money. Never accept an order against a number that was only true a moment ago."

Summary: Concepts Applied from 10-Week Course#

Concepts from the 10-week course in a national sale event
  • Week 1: Data at scale
    • Hot keys and skew — deal stock bounded by admission; sharded stock counters for hot non-deal products
    • Rate limiting — tokens per deal, limits per customer, device and address
  • Week 2: Failure-first design
    • Idempotency — one key from checkout through stock hold and payment
    • Timeouts and circuit breakers — UNKNOWN payments, per-bank breakers and bulkheads
  • Week 3: Messaging and async processing
    • Backpressure — a line per deal; priority lanes on the message bus
    • Transactional outbox — confirmed orders reach the warehouse exactly when they commit
  • Week 4: Caching
    • Thundering herd — versioned sale switch instead of synchronised expiry; jittered refreshes
    • Cache invalidation and multi-tier caching — version-keyed prices at app, edge and data-centre tiers
  • Week 5: Consistency and coordination
    • Strong consistency for stock holds and order transitions; hints everywhere else
  • Week 7: Search systems
    • Slow fields in the index, fast fields joined at serve time; head-query caching
  • Week 10: Production readiness
    • Capacity planning from forecasts, rehearsal and chaos testing; promises as capacity; sale-night SLOs

Why Flipkart Matters#

Why this is a different kind of hard problem
The peak is a date, and it's public
  • The worst minute of the year is announced weeks in advance. The challenge is preparing exactly: forecasting, rehearsing, staging and switching, not reacting
Scarcity is the product
  • A deal is worth something because there isn't enough. The system's job is to say "no" to almost everyone, quickly and fairly, and "yes" exactly as many times as there are units
The promise reaches into the physical world
  • Stock sits on shelves, payments run through banks, and parcels ride trucks with fixed departures. Every number on the screen is backed by something outside the software that can run out
The failure is on record
  • Flipkart's founders wrote down, the morning after, what went wrong in 2014: capacity, stock-outs, cancellations and prices

"Queue the deal, cache the store, and never accept an order against a number that was only true a moment ago."

Self-Assessment Checklist#

After studying this case study, you should be able to:

The opening and the deals:

The opening and the deals0/3 ready
  • Plan capacity for a scheduled spike from forecasts, and rehearse it with load and chaos tests
  • Switch a sale on by version instead of by expiry, and explain why that avoids a thundering herd
  • Design a per-deal line with tokens, a lottery window, limits and an honest answer for everyone else

Stock, checkout and payments:

Stock, checkout and payments0/3 ready
  • Hold stock at checkout with expiring, idempotent holds, and keep available + held + sold = on hand
  • Handle a double tap, a timed-out payment and a slow bank without a double charge or a lost order
  • Use an outbox and priority lanes so confirmed orders reach the warehouse first

Prices, search and promises:

Prices, search and promises0/3 ready
  • Serve prices from versioned price books that fail closed, and charge no more than shown
  • Keep prices out of the search index and join them at serve time
  • Compute delivery dates from wave capacity and cut-offs, reserve slots at checkout, and protect urgent trucks

Sources#

Flipkart:

Partners and case studies:

Reporting:

Design note: the deal line, stock holds, checkout, payment handling, price books, search split, promise allocation code, thresholds, dashboards and every number labelled as an assumption or estimate are an illustrative design for the problem, not a description of Flipkart's internal systems beyond what Flipkart and its partners have published. Request rates, orders a second, taps on a deal, conversion and checkout ratios, cache hit rates, hold durations, token counts and the Day 1 brief are assumptions. Rohan Mandal is a fictional customer.

Further Reading#

Related systems to study:

  • Read

    IRCTC Tatkal (Bonus Problem 11 in this series): a 10:00 spike on one train's seats, with admission control

  • Read

    BookMyShow (Bonus Problem 16): a site-wide waiting room when every seat is the scarce thing

  • Read

    Razorpay (Bonus Problem 18): payments, unknown outcomes and reconciliation from the gateway's side

  • Read

    Dream11 (Bonus Problem 25): a scheduled spike, forecast-driven capacity, and sharded counters

Course material:

  • Read

    The Week 2 capstone, a flash sale for MegaMart, is a smaller version of Deep Dives 2 and 3

Foundational concepts (this course):

  • Read

    Week 1, Day 3 — Rate Limiting at Scale

  • Read

    Week 1, Day 4 — Hot Keys and Skew

  • Read

    Week 2, Day 1 — Timeout Hell

  • Read

    Week 2, Day 2 — Idempotency in Practice

  • Read

    Week 2, Day 3 — Circuit Breakers

  • Read

    Week 3, Day 2 — Transactional Outbox

  • Read

    Week 3, Day 3 — Backpressure and Flow Control

  • Read

    Week 4, Day 2 — Cache Invalidation

  • Read

    Week 4, Day 3 — Thundering Herd

  • Read

    Week 4, Day 5 — Multi-Tier Caching

  • Read

    Week 5, Day 1 — Consistency Models

  • Read

    Week 7, Day 1 — Search Fundamentals

  • Read

    Week 10, Day 1 — SLIs, SLOs and SLAs

  • Read

    Week 10, Day 4 — Capacity Planning

"Queue the deal, cache the store, and never accept an order against a number that was only true a moment ago."

this lesson in shorts · 4

play them all →

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

End of Side quest 27 · Flipkart Big Billion Days
Clear the questFinish Flipkart.Stages clear as you read them. Marking complete unlocks after ~8 more min of reading.
  • 0/7stages cleared
design sheet · 7 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

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…