DEV Community

RONI DAS
RONI DAS

Posted on Originally published at systemdesign.academy

Blinkit System Design: Selling the Last Packet of Milk Exactly Once

From April to June 2026, Blinkit ran 2,443 dark stores, according to Eternal's results. Each store sold about ₹8.27 lakh a day. The average order was ₹518. Divide one by the other, and each store handled roughly 1,600 orders a day. Across the network, that is around 3.9 million orders a day. That second number is my own estimate from the reported averages. Blinkit did not publish it. But the size is what matters here.

Blinkit also reported something most system design write-ups never mention. It lost 1.8% of its order value to stock losses that quarter, about ₹308 crore. That is stock the system thought was on the shelf, but was not.

Put those two numbers together. They make quick commerce one of the best stock problems you can get in an interview. Here is how I would design it.

The store is the unit of everything

A Blinkit customer is not shopping a warehouse. They are shopping one small dark store near them. They can only buy what that one store has right now. So stock is not one number per product. It is one number per product per store, and there are 2,443 stores.

That points straight at the key design choice: split the stock data by store. Each store's stock lives together. A rush of orders in one area touches one store's data. It never touches the other 2,442.

It also makes growth boring, in the best way. Blinkit went from 1,544 stores to 2,443 in a year. That is about 900 new stores, more than two a day. If opening a store needed an engineer, that pace would be impossible. Here, a store's stock and its delivery area are just data. So opening a store means adding rows: the store, its delivery area drawn on a map, and its starting products. Nothing gets deployed.

An isometric diagram of one Blinkit order. The customer app sends the order to store routing, which picks one dark store. The stock service holds the items. Picking makes a short path list, and a delivery partner arrives as the bag closes.

Selling the last packet of milk exactly once

The hardest problem is the last item. Three customers near the same store add the last packet of milk. They all check out at the same moment. Say each one reads "1 left" and then writes "0 left". All three orders succeed. Ten minutes later, two of them get a cancellation.

The fix is to make the check and the change a single step. That is a conditional update:

UPDATE store_stock
   SET reserved = reserved + $3, version = version + 1
 WHERE store_id = $1 AND sku_id = $2
   AND on_hand - reserved >= $3
RETURNING version;   -- no row returned: sold out, tell the customer now
Enter fullscreen mode Exit fullscreen mode

I tested exactly this on PostgreSQL, with two sessions racing for the last item. The first one gets the hold. The second waits for the first to finish. Then the database checks the condition again, on the new row. The condition is now false, so the second gets no row back. Nothing is oversold, and the second customer can be told right away.

A hand-drawn sketch. Three customers try to hold the last packet of milk in one store at the same moment. One update succeeds for customer 1. The other two change zero rows and are told it is sold out.

Stock data is split by store, so this fight only ever happens inside one store's data. Three people fighting over one packet of milk in Koramangala never slow down anyone in Pune.

Two kinds of "in stock"

Blinkit had 31.8 million customers a month in that quarter. People browse far more than they buy. Every product tile asks if the item is in stock at your store. If all of those reads hit the true count, they would crowd out the writes that matter.

So there are two views. The truth lives in the store's stock service. Only writes and checkout touch it. The browse view is a copy built from stock events and kept in memory. It is allowed to be a second or two behind. Now and then it shows an item that just sold out. The hold at checkout catches that before the customer pays. Reads are fast and a little behind. The count is exact at the one moment that needs it.

Designing for a record that is wrong

That 1.8% loss is the real world disagreeing with the database. Items get damaged, expire, get stolen or get counted wrong. A design that trusts the count keeps selling an item that is not on the shelf. Then the order fails at picking.

So I would treat every physical touch as a check. Say a picker scans the shelf and the item is not there. The stock service records a "lost" event. It stops selling that item at that store right away. Staff count a few shelves every day to fix the rest. Every stock change is saved as an event, never written over. So the business can see where losses come from, by product and by store. It does not find the total only at the end of the quarter.

Changing who owns the stock while the stores keep selling

This one is rarely in any interview answer, and it really happened. Until August 2025, Blinkit ran as a marketplace. Sellers owned the stock in its dark stores. Then its parent, Eternal, became an Indian-owned and controlled company. India's foreign investment rules then allowed Blinkit to own stock. From 1 September 2025, it moved to owning its stock. Sellers had until 30 July to opt in. Stock was moved to Blinkit's company, valued at the landing price or through a goods received note.

For a system, that is a data migration while every store keeps selling. Every item on every shelf changed its legal owner and its cost. What makes that survivable is keeping ownership and cost in their own record. Keep them apart from the physical count. Picking, holds and the app only care how many items are on the shelf. Accounting and tax care who owns them. If those are separate, the owner can change underneath, and no customer notices.

What I would say in an interview

  • Make the store the unit of everything. Split stock data by store.
  • Hold stock at checkout with one conditional update. Give each hold an expiry.
  • Serve browsing from a fast copy that can be a little behind. Keep the truth for writes and checkout.
  • Assume the record is sometimes wrong. Turn every physical scan into a fix.
  • Keep ownership and cost apart from the physical count.

A note on honesty. Blinkit has not published how its systems are built. So everything above is a design that fits its public numbers. It is not a description of its code. I wrote the full version in the Blinkit system design walkthrough. It has the data model, the parts, the trade-offs and a source for every number. Zepto has published its stack, so the Zepto design is the companion read. And Swiggy covers food delivery, where the rider picks up from a restaurant instead.

Top comments (0)