Build Zepto | 10-Mins Quick Commerce | Inventory Management | System Design
An architectural deep dive into designing a 10-minute quick commerce application (like Zepto or Blinkit) with a primary focus on resilient multi-store Inventory Management, Proximity-Based Dark Store Routing, and Automated Order Splitting & Fulfillment.
1. Problem Statement & Status Quo: Quick Commerce vs. Traditional E-Commerce
Traditional e-commerce platforms (like Amazon) or food delivery apps (like Zomato) operate on fulfillment timelines ranging from hours to several days, typically pulling inventory from centralized warehouses. In stark contrast, Quick Commerce (10-minute delivery) guarantees hyper-fast turnaround times.
To achieve this, platforms rely on a decentralized network of Dark Stores—micro-fulfillment centers positioned strategically across urban grids.
Real-Life Analogy
The Neighborhood Warehouse Network: Imagine you need groceries instantly. Instead of ordering from a massive warehouse 50 miles away, you place an order routed to three different mini-shops scattered across your neighborhood. Shop A has your milk, Shop B has your bread, and Shop C has your snacks. Three different local runners pick up items from their respective shops and arrive at your doorstep almost simultaneously.
The Core Challenges:
- Decentralized Inventories: Stock availability varies dynamically across multiple dark stores.
- Partial Fulfillment & Order Splitting: A user's cart containing multiple items might not exist in a single nearest dark store. The system must split the order across secondary and tertiary dark stores.
- Dynamic Replenishment: Tracking thresholds or running schedule-based routines to restock depleted items without disrupting active user orders.
2. Functional Requirements
To build a robust inventory and quick-commerce engine, the system must satisfy the following core functional requirements:
- Inventory Management: System administrators must be able to add, update, and remove products/items organized by categories (e.g., Electronics, Groceries, Essentials).
- Automated Replenishment Strategies: Extensible restocking mechanisms (Threshold-based or Weekly batch restocking) to replenish depleted dark store stocks.
- Multi-Store Inventory Storage: Support for abstract and concrete inventory storage backends (e.g., In-Memory Hash Maps, Relational Database Stores, or File systems).
- Proximity-Based Product Discovery: Users should only view items available in dark stores within a specific operational radius (e.g., ).
- Smart Order Splitting: If a single dark store cannot fulfill an entire order, the system must automatically split the order across multiple nearby dark stores and assign multiple delivery agents accordingly.
3. High-Level System Architecture & Component Flow
The system coordinates interactions between users, the central orchestration engine, spatial store managers, and distributed dark store inventories.
4. Core Domain Models: Products & Factories
At the foundational layer, we define immutable models and a software Factory Pattern to instantiate products cleanly without littering business logic with new statements.
Product Model & Product Factory
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
#include <cmath>
#include <algorithm>
class Product {
private:
int sku;
std::string name;
double price;
public:
Product(int sku, std::string name, double price)
: sku(sku), name(name), price(price) {}
int getSku() const { return sku; }
std::string getName() const { return name; }
double getPrice() const { return price; }
};
class ProductFactory {
public:
static std::shared_ptr<Product> createProduct(int sku) {
// In real-world enterprise applications, this fetches from a Database/Cache.
if (sku == 1001) return std::make_shared<Product>(1001, "Apple", 20.0);
if (sku == 1002) return std::make_shared<Product>(1002, "Banana", 10.0);
if (sku == 1003) return std::make_shared<Product>(1003, "Chocolate", 50.0);
if (sku == 2001) return std::make_shared<Product>(2001, "T-Shirt", 500.0);
return std::make_shared<Product>(sku, "Generic Item", 100.0);
}
};
5. Inventory Storage & Management (Bridge Pattern)
To decouple how inventory is managed (high-level business rules) from where inventory is stored (low-level persistence like Database vs. In-Memory Maps), we implement a variation of the Bridge Design Pattern.
Abstract Inventory Store & Concrete DB Store
class InventoryStore {
public:
virtual ~InventoryStore() = default;
virtual void addProduct(std::shared_ptr<Product> product, int quantity) = 0;
virtual void removeProduct(int sku, int quantity) = 0;
virtual int checkStock(int sku) const = 0;
virtual std::vector<std::pair<int, int>> listAvailableProducts() const = 0;
};
class DBInventoryStore : public InventoryStore {
private:
// SKU -> Quantity Mapping for O(1) lookups
std::unordered_map<int, int> stocks;
// SKU -> Product Object Mapping
std::unordered_map<int, std::shared_ptr<Product>> products;
public:
void addProduct(std::shared_ptr<Product> product, int quantity) override {
int sku = product->getSku();
products[sku] = product;
stocks[sku] += quantity;
}
void removeProduct(int sku, int quantity) override {
if (stocks.find(sku) != stocks.end()) {
stocks[sku] -= quantity;
if (stocks[sku] <= 0) {
stocks.erase(sku);
products.erase(sku);
}
}
}
int checkStock(int sku) const override {
auto it = stocks.find(sku);
if (it != stocks.end()) {
return it->second;
}
return 0;
}
std::vector<std::pair<int, int>> listAvailableProducts() const override {
std::vector<std::pair<int, int>> available;
for (const auto& pair : stocks) {
if (pair.second > 0) {
available.push_back({pair.first, pair.second});
}
}
return available;
}
};
6. Inventory Managers & Replenishment Strategies (Strategy Pattern)
An InventoryManager delegates raw operations to the underlying store while handling higher-level validations and factory creations. Restocking follows the Strategy Design Pattern.
class InventoryManager {
private:
std::shared_ptr<InventoryStore> store;
public:
InventoryManager(std::shared_ptr<InventoryStore> store) : store(store) {}
void addStock(int sku, int quantity) {
auto product = ProductFactory::createProduct(sku);
store->addProduct(product, quantity);
}
void removeStock(int sku, int quantity) {
store->removeProduct(sku, quantity);
}
int checkStock(int sku) const {
return store->checkStock(sku);
}
std::vector<std::pair<int, int>> getAvailableProducts() const {
return store->listAvailableProducts();
}
};
class ReplenishStrategy {
public:
virtual ~ReplenishStrategy() = default;
virtual void replenish(std::shared_ptr<InventoryManager> manager, const std::unordered_map<int, int>& itemsToReplenish) = 0;
};
class ThresholdReplenishStrategy : public ReplenishStrategy {
private:
int threshold;
public:
ThresholdReplenishStrategy(int threshold) : threshold(threshold) {}
void replenish(std::shared_ptr<InventoryManager> manager, const std::unordered_map<int, int>& itemsToReplenish) override {
for (const auto& item : itemsToReplenish) {
int sku = item.first;
int targetQty = item.second;
int currentStock = manager->checkStock(sku);
if (currentStock < threshold) {
manager->addStock(sku, targetQty);
std::cout << "[Replenish] SKU " << sku << " fell below threshold (" << threshold
<< "). Added " << targetQty << " units.\n";
}
}
}
};
7. Dark Store & Spatial Proximity Management
A Dark Store encapsulates physical coordinates (x, y), an inventory manager, and a replenishment strategy. The Dark Store Manager acts as a singleton registry capable of querying stores within a specified radius.
class DarkStore {
private:
std::string name;
double x, y; // Geographic coordinates
std::shared_ptr<InventoryManager> inventoryManager;
std::shared_ptr<ReplenishStrategy> replenishStrategy;
public:
DarkStore(std::string name, double x, double y) : name(name), x(x), y(y) {
auto store = std::make_shared<DBInventoryStore>();
inventoryManager = std::make_shared<InventoryManager>(store);
}
std::string getName() const { return name; }
double getX() const { return x; }
double getY() const { return y; }
std::shared_ptr<InventoryManager> getInventoryManager() const { return inventoryManager; }
double distanceTo(double ux, double uy) const {
return std::sqrt(std::pow(x - ux, 2) + std::pow(y - uy, 2));
}
void setReplenishStrategy(std::shared_ptr<ReplenishStrategy> strategy) {
replenishStrategy = strategy;
}
// Delegation wrappers (Principle of Least Knowledge)
void addStock(int sku, int qty) { inventoryManager->addStock(sku, qty); }
int checkStock(int sku) const { return inventoryManager->checkStock(sku); }
void removeStock(int sku, int qty) { inventoryManager->removeStock(sku, qty); }
};
class DarkStoreManager {
private:
static DarkStoreManager* instance;
std::vector<std::shared_ptr<DarkStore>> stores;
DarkStoreManager() = default;
public:
static DarkStoreManager* getInstance() {
if (!instance) instance = new DarkStoreManager();
return instance;
}
void registerDarkStore(std::shared_ptr<DarkStore> store) {
stores.push_back(store);
}
std::vector<std::shared_ptr<DarkStore>> getNearByDarkStores(double ux, double uy, double maxDistance) {
std::vector<std::pair<double, std::shared_ptr<DarkStore>>> distStores;
for (auto& store : stores) {
double d = store->distanceTo(ux, uy);
if (d <= maxDistance) {
distStores.push_back({d, store});
}
}
// Sort by proximity (closest first)
std::sort(distStores.begin(), distStores.end(), [](const auto& a, const auto& b) {
return a.first < b.first;
});
std::vector<std::shared_ptr<DarkStore>> result;
for (const auto& pair : distStores) {
result.push_back(pair.second);
}
return result;
}
};
DarkStoreManager* DarkStoreManager::instance = nullptr;
8. User Cart, Orders, & Smart Multi-Store Splitting
When an order is placed, the OrderManager evaluates the user's cart against the ordered list of nearby dark stores. If the nearest store has all items, a single delivery partner is assigned. Otherwise, the order is intelligently split across multiple stores.
User & Cart Models
class Cart {
private:
// Pair of <Product, Quantity>
std::vector<std::pair<std::shared_ptr<Product>, int>> items;
public:
void addItem(int sku, int quantity) {
auto product = ProductFactory::createProduct(sku);
items.push_back({product, quantity});
}
const std::vector<std::pair<std::shared_ptr<Product>, int>>& getItems() const {
return items;
}
double getTotal() const {
double total = 0;
for (const auto& item : items) {
total += item.first->getPrice() * item.second;
}
return total;
}
};
class User {
private:
std::string name;
double x, y;
Cart cart;
public:
User(std::string name, double x, double y) : name(name), x(x), y(y) {}
std::string getName() const { return name; }
double getX() const { return x; }
double getY() const { return y; }
Cart& getCart() { return cart; }
};
struct DeliveryPartner {
std::string name;
};
class Order {
private:
static int nextId;
int orderId;
std::string userName;
std::vector<std::pair<int, int>> orderedItems; // SKU, Qty
std::vector<DeliveryPartner> partners;
double totalAmount;
public:
Order(std::string userName, double totalAmount)
: orderId(++nextId), userName(userName), totalAmount(totalAmount) {}
void addPartner(const DeliveryPartner& partner) { partners.push_back(partner); }
void addItem(int sku, int qty) { orderedItems.push_back({sku, qty}); }
int getId() const { return orderId; }
const std::vector<DeliveryPartner>& getPartners() const { return partners; }
double getTotal() const { return totalAmount; }
};
int Order::nextId = 0;
Order Manager & Splitting Algorithm
class OrderManager {
private:
static OrderManager* instance;
std::vector<std::shared_ptr<Order>> orders;
OrderManager() = default;
public:
static OrderManager* getInstance() {
if (!instance) instance = new OrderManager();
return instance;
}
std::shared_ptr<Order> placeOrder(User& user) {
auto dsManager = DarkStoreManager::getInstance();
auto nearbyStores = dsManager->getNearByDarkStores(user.getX(), user.getY(), 5.0);
if (nearbyStores.empty()) {
std::cout << "[Error] No dark stores found within 5km!\n";
return nullptr;
}
const auto& cartItems = user.getCart().getItems();
// Check if the closest store can fulfill the entire order
bool allInFirst = true;
auto firstStore = nearbyStores[0];
for (const auto& item : cartItems) {
int sku = item.first->getSku();
int needed = item.second;
if (firstStore->checkStock(sku) < needed) {
allInFirst = false;
break;
}
}
auto order = std::make_shared<Order>(user.getName(), user.getCart().getTotal());
if (allInFirst) {
std::cout << "[Fulfillment] Entire order fulfilled by single store: " << firstStore->getName() << "\n";
for (const auto& item : cartItems) {
firstStore->removeStock(item.first->getSku(), item.second);
order->addItem(item.first->getSku(), item.second);
}
order->addPartner(DeliveryPartner{"Partner-A (Store " + firstStore->getName() + ")"});
} else {
std::cout << "[Fulfillment] Order split across multiple dark stores!\n";
// Map to track remaining items to fulfill: SKU -> Quantity
std::unordered_map<int, int> remainingItems;
for (const auto& item : cartItems) {
remainingItems[item.first->getSku()] = item.second;
}
int partnerCount = 1;
for (auto store : nearbyStores) {
if (remainingItems.empty()) break;
bool fulfilledAnything = false;
std::vector<int> toErase;
for (auto& pair : remainingItems) {
int sku = pair.first;
int neededQty = pair.second;
int availableQty = store->checkStock(sku);
if (availableQty > 0) {
int takeQty = std::min(neededQty, availableQty);
store->removeStock(sku, takeQty);
order->addItem(sku, takeQty);
remainingItems[sku] -= takeQty;
if (remainingItems[sku] <= 0) {
toErase.push_back(sku);
}
fulfilledAnything = true;
}
}
for (int sku : toErase) {
remainingItems.erase(sku);
}
if (fulfilledAnything) {
std::string pName = "Partner-" + std::to_string(partnerCount++) + " (Store " + store->getName() + ")";
order->addPartner(DeliveryPartner{pName});
std::cout << " > Assigned " << pName << "\n";
}
}
if (!remainingItems.empty()) {
std::cout << "[Warning] Some items could not be fully fulfilled due to stock limits.\n";
}
}
orders.push_back(order);
return order;
}
};
OrderManager* OrderManager::instance = nullptr;
9. Comprehensive Execution Flow Walkthrough
Below is the complete client orchestrator setup (main) demonstrating dark store registration, inventory loading, user cart creation, proximity checks, and multi-store order splitting execution.
class ZeptoApp {
public:
static void initializeSystem() {
auto dsManager = DarkStoreManager::getInstance();
auto storeA = std::make_shared<DarkStore>("Store-A (Downtown)", 0.0, 0.0);
storeA->addStock(1001, 5); // 5 Apples
storeA->addStock(1002, 2); // 2 Bananas
auto storeB = std::make_shared<DarkStore>("Store-B (Uptown)", 2.0, 2.0);
storeB->addStock(1001, 3); // 3 Apples
storeB->addStock(1003, 10); // 10 Chocolates
auto storeC = std::make_shared<DarkStore>("Store-C (Suburbs)", 1.0, 1.0);
storeC->addStock(1002, 5); // 5 Bananas
dsManager->registerDarkStore(storeA);
dsManager->registerDarkStore(storeB);
dsManager->registerDarkStore(storeC);
}
};
int main() {
ZeptoApp::initializeSystem();
User user("Aditya", 1.0, 0.5);
std::cout << "User " << user.getName() << " opened Quick Commerce App.\n\n" ;
// User adds items: 4 Apples (1001), 3 Bananas (1002), 2 Chocolates (1003)
user.getCart().addItem(1001, 4);
user.getCart().addItem(1002, 3);
user.getCart().addItem(1003, 2);
auto order = OrderManager::getInstance()->placeOrder(user);
if (order) {
std::cout << "\n================ ORDER SUMMARY ================\n";
std::cout << "Order ID : " << order->getId() << "\n";
std::cout << "Total Amount : Rs. " << order->getTotal() << "\n";
std::cout << "Assigned Partners (" << order->getPartners().size() << "):\n";
for (const auto& p : order->getPartners()) {
std::cout << " - " << p.name << "\n";
}
std::cout << "===============================================\n";
}
return 0;
}
10. Summary and Key Takeaways
- Decentralized Inventory Models: Quick commerce requires managing localized stock pools across multiple dark stores rather than a single centralized warehouse.
- Bridge Pattern Architecture: Decoupling
InventoryManager(business logic) fromInventoryStore(persistence mechanism) enables seamless switching between in-memory maps, SQL, or NoSQL databases. - Strategy Pattern for Replenishment: Restocking logic (Threshold-based vs. Weekly scheduled batches) is encapsulated into pluggable strategies, ensuring high extensibility.
- Proximity Filtering: Spatial filtering evaluates user GPS coordinates against dark store radii using distance formulas to filter relevant micro-fulfillment centers.
- Smart Order Splitting: When cart items exceed single-store stock availability, greedy inventory allocation traverses secondary and tertiary stores, orchestrating multi-partner delivery dispatches.
- Principle of Least Knowledge: Components delegate tasks downward cleanly (e.g.,
DarkStoredelegates toInventoryManager, which delegates toInventoryStore), preventing tight coupling and circular dependencies.