Design problems
E-commerce & marketplacesMediumSDE-2 30 min

Design a reseller social-commerce app (Meesho)

Asked at
FlipkartMeeshoMyntraShareChat

The interviewer asks

“Design a social-commerce app like Meesho where small suppliers list products, resellers share them on WhatsApp with their own margin, and buyers in small towns order with cash on delivery. Focus on the catalog, sharing, ordering and the economics of low-value orders.”

What a strong answer looks like

A strong answer starts by naming what is different from Flipkart or Amazon: very low order values (₹200-400), mostly first-time internet users on cheap Android phones and 4G that drops often, cash on delivery and high return-to-origin (RTO) rates, and a share-to-WhatsApp loop where the reseller adds a margin. The core is a catalog service with heavily compressed images served from a CDN, a share service that creates a short link carrying the reseller ID and margin, an order service whose price is computed on the server (supplier price + reseller margin + shipping) and never trusted from the client, and a margin ledger that pays resellers only after the return window closes. Around it sit a personalised feed, COD risk scoring to cut RTO, and a logistics integration with multiple 3PL partners. Spend the deep dives on cost per order (images, bandwidth, compute), the reseller margin ledger and COD/RTO risk, because those decide whether the business survives.

Concepts to mention

share link with reseller marginserver-side price computationimage variants and CDN cost per orderappend-only margin ledgeridempotency key on order placementCOD / RTO risk scoringoutbox + Kafka for order eventsmulti-3PL logistics integrationcandidate generation + light ranking feedreturn window before payout

1 Requirements

Functional

  • Suppliers upload products (images, sizes, price, stock) in bulk.
  • Users browse a personalised feed and search in English and regional languages.
  • A reseller shares a product or a collection to WhatsApp with a margin added on top of the supplier price.
  • A buyer (or the reseller on the buyer's behalf) places an order with COD or UPI.
  • Order tracking, cancellations and returns, with the reseller margin adjusted on returns.
  • Resellers see earnings and get payouts to their bank account or UPI ID after the return window.
  • Suppliers see orders, print shipping labels and get settlements.

Non-functional

  • Feed and product pages load in under 2 s on a 3G/weak 4G connection; first image in under 1 s.
  • Image bytes per product card under 30 KB (WebP/AVIF, sized per device).
  • 99.95% availability for browse and checkout; festive sale peaks of 10x normal traffic.
  • Order placement p99 under 500 ms at the server; no double orders on retries.
  • Money is exact: margins and settlements reconcile to the paisa every day.
  • Cost per order must stay very low because average order value is only ~₹300.

Out of scope (say it out loud)

  • Building our own logistics fleet (we integrate with 3PL partners).
  • Ad platform for suppliers.
  • Supplier onboarding KYC and GST verification details.
  • Search ranking model internals.

2 Estimates

Users~150M monthly, ~30M daily activeAssume a large Tier-2/3 user base; DAU about 20% of MAU.
Orders~5M/day, peak ~250/s (sale peak ~1,500/s)5M ÷ 86,400 ≈ 58/s average; evening peak ×4 ≈ 250/s; festive sale ×6 on top.
Feed reads~50k/s peak30M DAU × ~40 feed/product calls/day = 1.2B/day ≈ 14k/s; ×3-4 at evening peak.
Catalog size~100M active SKUs, ~500M imagesMillions of suppliers × tens of products × sizes; ~5 images per product.
Image storage~100 TB originals + ~150 TB variants500M originals × ~200 KB = 100 TB; 4-5 resized variants of 20-60 KB each.
CDN egress~3 PB/month30M DAU × ~200 cards/day × 25 KB × 30 days ≈ 4.5 PB; CDN cache hit ratio >95%. Every KB saved per card matters for cost.
Share links~20M/dayResellers share 3-5x more than they order; short link resolution ~1k/s peak.

3 API

EndpointWhat it does
GET/v1/feed?cursor=&lang=hiPersonalised product feed, cursor-paginated, returns image URLs sized for the device.
GET/v1/products/{id}Product detail with supplier price, sizes, stock flag and delivery estimate for the user's pincode.
POST/v1/sharesCreate a share for {product_ids, margin}; returns short link + pre-rendered collage image for WhatsApp.
GET/s/{code}Resolve a share link; opens the app or a lightweight web page with the reseller's price.
POST/v1/ordersPlace an order {sku, qty, address_id, payment_mode, share_code?}; Idempotency-Key header required.
POST/v1/orders/{id}/returnRequest a return; reverses the reseller margin if accepted.
GET/v1/reseller/earningsPending, confirmed and paid margin for the reseller.
POST/v1/supplier/catalog:bulkUploadSupplier uploads a CSV + images; processed asynchronously.

4 Data model

products / skusPostgres (sharded by supplier_id) + Elasticsearch
product_id (PK)supplier_idtitlecategory_idattributes JSONB; sku_idproduct_idsizesupplier_pricestock

Postgres is the source of truth; Elasticsearch is a read model fed by CDC for search and filters. Stock is a counter per SKU.

imagesS3 + CDN
image_idproduct_idoriginal_keyvariants {w160w320w640} in WebP/AVIF

Variants generated once at upload; URLs are immutable and cached forever at the CDN.

sharesDynamoDB / Cassandra
code (PK)reseller_idproduct_idsmargin_paisecreated_atexpires_at

Key-value lookup by code; write-heavy and never updated. TTL after ~90 days.

ordersPostgres (sharded by buyer_id)
order_id (PK)buyer_idreseller_id?sku_idsupplier_pricemarginshipping_feetotalpayment_modestatusidempotency_key (unique)created_at

Prices are copied into the order at placement so later price changes do not alter it.

margin_ledgerPostgres
entry_idreseller_idorder_idamount_paisetype (accrue|reverse|payout)statuscreated_at

Append-only, double-entry style. Balance = sum of entries. Never update a balance column in place.

user_eventsKafka → data lake (S3 + Spark)
user_idevent (view|share|order)product_idts

Feeds the recommendation models and the RTO risk model.

feed cacheRedis
feed:{user_id} → list of product_ids (next 200)

Pre-computed candidates refreshed every few hours; ranking applied on read.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAEXTERNALimages/s/{code}origin fetchfeed, searchcreate/resolveplace orderCOD allowed?OrderPlacedaccrue margincreate shipmentresized variantsdelivered / RTOAndroid app / Lite webWhatsApp (shared link)API gatewayCDN (images, linkpages)Share serviceOrder serviceCatalog + feed serviceMargin ledger +payoutsImage pipelineworkersCOD / RTO risk modelKafka (order/userevents)Postgres (orders,ledger)S3 (images)Elasticsearch(search)Redis (feed, stock)3PL partners + UPI PSP
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Reseller shares a product with margin

  1. 1Reseller taps Share on a product and sets a margin of ₹80.
  2. 2App calls POST /v1/shares; share service validates the margin against a cap (e.g. not more than 100% of the supplier price).
  3. 3Share service writes {code, reseller_id, product_id, margin} to the key-value store and returns a short link.
  4. 4A worker renders a collage image with the final price (supplier price + margin) and caches it at the CDN.
  5. 5App opens WhatsApp with the image and link; the reseller sends it to family or a group.

Buyer orders from a shared link with COD

  1. 1Buyer opens /s/{code}; the CDN serves a light page, and the app deep-links if installed.
  2. 2Order service loads the share, the SKU's current supplier price and the shipping fee for the buyer's pincode, and computes the total on the server.
  3. 3Order service calls the RTO risk model with pincode, address quality, buyer history and order value; high risk means COD is disabled and only prepaid UPI is offered.
  4. 4Order is inserted with the idempotency key unique constraint; stock is decremented with a conditional update (stock >= qty).
  5. 5OrderPlaced event goes to Kafka via an outbox table; consumers create the 3PL shipment, notify the supplier and accrue the reseller margin as 'pending'.
  6. 6On delivery plus the return window (e.g. 7 days) the ledger turns the margin 'confirmed'; a weekly payout job pays resellers via bank/UPI payout APIs.

Supplier bulk upload

  1. 1Supplier uploads a CSV and image zip to a pre-signed S3 URL.
  2. 2Upload job validates rows, checks duplicate images with a perceptual hash, and flags banned items.
  3. 3Image workers create WebP/AVIF variants at 3-4 widths and strip metadata.
  4. 4Products are written to Postgres; CDC streams them to Elasticsearch and the feed candidate generator.

7 Deep dives

Cost per order: images and bandwidth

With an average order of about ₹300 and a take rate close to zero commission, the platform earns very little per order, so infrastructure cost per order is a design constraint, not an afterthought. Images are the biggest cost: a user scrolls 100-300 product cards a day and every card is a picture. Generate variants once at upload (160, 320, 640 px widths) in WebP and AVIF, pick the variant on the client from screen width and network type, and target 15-30 KB per card instead of 150 KB. Use immutable URLs with a content hash so the CDN and the phone can cache for a year. Lazy-load below the fold and prefetch only the next screen. Deduplicate supplier images with a perceptual hash because many suppliers upload the same photo. Keep the API payload small too: send product IDs plus a compact card object, not the full product. Measuring this gives a strong answer: if 30M users load 200 cards a day, cutting 10 KB per card saves 60 TB of egress a day. The same thinking applies to compute: pre-compute feed candidates in batch, cache aggressively, and run ranking on cheap CPU models rather than calling heavy models per request.

Reseller margin ledger

The reseller earns the margin only when the buyer keeps the product. So the ledger must model money that moves through states: pending when the order is placed, confirmed after delivery plus the return window, reversed on return or RTO, and paid when the payout runs. Model it as an append-only ledger of entries (accrue, reverse, payout) rather than a balance column that you update. The balance is the sum of entries; a materialised balance can be cached but is always rebuildable. Every entry carries the order_id and a type, with a unique constraint on (order_id, type) so a replayed Kafka event cannot accrue twice; this is how you get effectively-once from at-least-once delivery. Payouts run as a batch: select confirmed, unpaid entries, create one payout per reseller with an idempotency key, call the bank payout API, and write the payout entry only when the bank confirms. A daily reconciliation job compares the ledger with bank payout reports and with order states, and raises alerts for any mismatch. Store money as integer paise, never floats.

COD and RTO risk

Most orders in Tier-2/3 towns are cash on delivery, and a large share of COD orders are refused at the door or the address is not found. Each RTO costs forward plus reverse shipping, often more than the platform earned. So decide at checkout whether to allow COD. The order service calls a risk service with features: pincode RTO history, address completeness score (does it have a landmark, a house number), the buyer's past refusals, order value, time of day and whether the order came through a reseller with a good history. The model returns a score in a few milliseconds from an in-memory feature store; above a threshold COD is hidden and prepaid UPI is offered, perhaps with a small discount. Also confirm high-risk orders with an automated IVR or WhatsApp message before shipping. The decision must fail open in a controlled way: if the risk service times out (budget 50 ms), fall back to a simple rule based on pincode, not to blocking all orders.

Feed for low-intent browsing users

Many users do not search; they scroll. The feed is a two-stage system. Offline, a batch job (Spark) builds candidates per user from category affinity, regional popularity, price band and what similar users shared or bought, and stores the top few hundred product IDs in Redis. Online, the feed service takes the next page of candidates, removes out-of-stock and already-seen items, and ranks them with a light model that uses fresh signals from the last session (clicked categories, price range). Cold-start users get regional bestsellers in their language. Product titles and attributes are translated or transliterated so a user in Lucknow and one in Coimbatore both see readable text. Keep the feed cursor stateless (encode the offset and a seed) so any server can serve the next page.

8 Trade-offs

Server-side pricing vs trusting the share link

The share link carries a margin, but the client never sends the final price. The order service recomputes supplier price + margin + shipping on every order. This costs one extra lookup but closes an easy fraud path where a modified client or link changes the price. If the supplier price changed since the share, show the new total before confirmation.

Postgres for orders and ledger vs a NoSQL store

Orders and money need transactions, unique constraints for idempotency and easy reconciliation queries, so Postgres sharded by buyer_id (or Citus/Vitess) fits. Shares, events and feed candidates are simple key lookups at high volume, so a key-value store and Redis are cheaper there. Using one database for everything either wastes money or loses correctness.

Pay margin after return window vs instantly

Paying immediately feels good for resellers but creates clawback debt when orders are returned, and some resellers disappear. Paying weekly after delivery + return window is safer. A middle path is showing 'pending' earnings instantly and offering an early payout of part of it to trusted resellers.

Native app vs lightweight web for shared links

Many buyers do not have the app. A server-rendered web page under 100 KB, cached at the CDN, converts better than forcing an install on low storage phones. The app is still the main surface for repeat users.

9 Bottlenecks

Festive sale traffic on a few hot products

During a sale a handful of products get most views and orders. Product pages are served from cache (Redis + CDN for static parts). Stock for hot SKUs becomes a hot row; use a conditional decrement in Redis with periodic sync to Postgres, or split stock into a few buckets, and reconcile asynchronously.

Image processing backlog after big supplier uploads

Bulk uploads of millions of images can swamp workers. Put image jobs on a queue with per-supplier fairness, autoscale workers on queue depth, and show products only after the 320 px variant is ready.

Search in many languages

Queries come in Hinglish and regional scripts with spelling errors. Use transliteration, synonym dictionaries and per-language analyzers in Elasticsearch; at 10x, split indexes by category and add a query-result cache for popular queries.

10 Failure handling

3PL partner API down

Shipment creation is asynchronous from a Kafka consumer, so orders still get placed. The consumer retries with backoff, then routes to a second partner serving that pincode. Orders waiting too long raise alerts to operations.

Duplicate order on a flaky network

The app sends an Idempotency-Key per checkout attempt. A retry with the same key hits the unique constraint and returns the original order, so the buyer is not charged or shipped twice.

Ledger consumer replays events

Unique (order_id, type) on ledger entries makes replay harmless. Daily reconciliation catches anything missed, for example an order delivered but no confirm entry.

11 What impresses

  • Treats cost per order as a first-class requirement and does the egress math for image sizes.
  • Explains that margin is pending until delivery plus return window and models reversals in the ledger.
  • Uses an RTO risk model to decide COD per order, with a fail-safe fallback if the model is slow.
  • Designs for weak networks: small payloads, lite web page for shared links, offline-tolerant retries with idempotency keys.
  • Mentions regional language search and transliteration without being asked.
  • Keeps money in integer paise with daily reconciliation against bank payout reports.

12 Mistakes

  • Designing a generic Amazon clone and missing the reseller margin and WhatsApp sharing loop.
  • Trusting the price sent by the client or encoded in the link.
  • Paying resellers immediately and ignoring returns and RTO.
  • Ignoring image size and bandwidth cost on low-end devices.
  • Updating a balance column in place with no ledger, so money cannot be audited.
  • Treating COD as just another payment mode with no risk handling.

13 Follow-ups

Q1How do you stop a reseller from setting an absurd margin and hurting conversion or trust?
Q2A supplier changes the price after thousands of shares. What does the buyer see?
Q3How would you detect fake orders placed by resellers to game incentives?
Q4How do you choose between 3PL partners for a pincode?
Q5How would you run this on half the infrastructure budget?
Q6How do you handle partial returns in a multi-item order for the ledger?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free