Design problems
Search & discoveryMediumSDE-2 35 min

Design product catalogue search (Myntra / Flipkart)

Asked at
AmazonFlipkartMeeshoMyntraNykaaWalmart Global Tech

The interviewer asks

“Design the product search for a large e-commerce site like Flipkart or Myntra. Users type queries like "red kurta under 1000" or "iphone 15 128gb", apply filters such as brand, size and price, and expect relevant, in-stock products sorted well.”

What a strong answer looks like

A strong answer treats product search as a pipeline: query understanding turns free text into structure (category = kurta, colour = red, price < 1000), hybrid retrieval fetches candidates from an inverted index (BM25 with field boosts) plus a vector index for semantic matches, and a learning-to-rank model re-ranks the top few hundred on relevance and business signals (conversion, ratings, stock, margin, returns). Faceted filters come from index aggregations. The catalogue is the source of truth in a database; changes flow through CDC and Kafka to indexers, with a full rebuild via alias swap for schema changes, and fast partial updates for price and stock. Strong candidates separate the SKU/variant model (one style, many sizes) from search documents, keep price and stock fresh during sales, and talk about evaluation (NDCG offline, conversion A/B online) and the zero-results problem.

Concepts to mention

Query understanding: attribute tagging and category classificationInverted index with field boosts (BM25)Hybrid lexical + vector retrievalLearning-to-rank re-rankingFaceted search with aggregationsStyle vs SKU document modellingCDC and Kafka to keep the index freshIndex alias swap for full rebuildsNDCG offline, conversion A/B online

1 Requirements

Functional

  • Free-text search across ~200M products with typo tolerance and synonyms ("sneakers" = "sports shoes")
  • Faceted filters with counts: brand, price range, size, colour, rating, discount, delivery by tomorrow
  • Sort by relevance, price, newest, popularity, discount
  • Understand structured intent in text: "red kurta under 1000", "samsung 5g phone"
  • Show only sellable products; out-of-stock items are hidden or pushed down
  • Category browse pages use the same engine (search with an empty query plus a category filter)

Non-functional

  • p99 search latency under 250 ms at the backend
  • Peak ~50k QPS during Big Billion Days, ~10k QPS normal
  • Price and stock changes visible in search within ~1 minute; new products within ~15 minutes
  • Availability 99.95%; degrade to simpler ranking rather than fail
  • Relevance measured offline (NDCG@10) and online (search conversion rate)

Out of scope (say it out loud)

  • Typeahead suggestions (separate design)
  • Recommendations on the home page and product page
  • Seller catalogue onboarding and image moderation
  • Cart, checkout and payments

2 Estimates

Catalogue size~200M SKUs, ~50M searchable stylesMarketplace with ~1M sellers; one style (a kurta design) has ~4-6 size/colour variants; index one document per style with variant data nested
Index size~200-300 GB primary50M docs x ~4-6 KB (text fields, attributes, variant arrays, ranking features) ~ 250 GB; with 2 replicas ~750 GB across the cluster
Search QPS~10k/s normal, ~50k/s sale peak~400M searches/day in a sale week / 86,400 ~ 4.6k/s average; first hour of a sale spikes ~10x
Update rate~5-20k doc updates/s in salesPrices and stock change constantly; during sales ~10M SKUs change price or stock in the first hours; batch partial updates
Shards~20-30 primary shardsKeep shards ~10-20 GB for fast recovery and query; 250 GB / ~10 GB ~ 25 shards, each with 2 replicas, spread over ~25-40 data nodes
Vector index~50M x 384 dims ~ 75 GB float3250M x 384 x 4 bytes ~ 77 GB; with int8 quantisation ~ 20 GB, fits in RAM on a few nodes

3 API

EndpointWhat it does
GET/v1/search?q={text}&filters=brand:puma,size:M&sort=relevance&page_token=Search or browse: returns products, facets with counts, applied query interpretation and next page token
GET/v1/browse/{category_id}?filters=&sort=Category listing using the same engine with an empty query
POST/internal/catalog/eventsInternal: product created/updated, price change, stock change (produced via CDC to Kafka)

4 Data model

products / styles (source of truth)MySQL/Postgres (sharded by product_id) or a document DB
style_id (PK)titlebrandcategory_idattributes JSONdescriptionimages[]seller_idcreated_at

Written by the catalogue service. Search never reads it at query time.

sku_offersPostgres + Redis cache
sku_id (PK)style_idsizecolourseller_idpricemrpstock_qtywarehouse_ids[]

Fast-changing: price and stock. Owned by pricing and inventory services.

search_docElasticsearch / OpenSearch
style_idtitlebrand (keyword)category_pathattributes (keyword fields)min_pricemax_discountsizes_in_stock[]colours[]ratingrating_countpopularity_7dconversion_ratein_stockdelivery_pincodes_tierembedding (dense_vector)

Denormalised: one doc per style with the fields needed for matching, filtering, faceting and first-stage ranking. Analysers: stemming, synonyms, edge n-grams on title; keyword fields for facets.

query_understanding_dictRedis / in-memory, built offline
phrase -> {categorybrandattributesynonym}e.g. 'kurta' -> category 'Women > Ethnic > Kurtas'

Mined from query logs and catalogue attribute values; refreshed daily.

search_logsKafka -> data lake
queryinterpretationresults shown with positionsclicksadd-to-cartorders

Feed for LTR training, synonym mining and zero-result analysis.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAGET /searchparse queryBM25 + filters + facetssemantic kNNtop ~400CDCbulk upsertsnew / changed textWeb / mobile appCDN + API gatewayQuery understandingSearch serviceRe-ranker (LTR model)Catalogue / price /stockIndexer + enrichmentEmbedding workersKafka (CDC events)Elasticsearch clusterResult + featurecacheVector index (ANN)Catalogue DB
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Search "red kurta under 1000"

  1. 1App calls GET /v1/search?q=red kurta under 1000; gateway forwards to the search service.
  2. 2Query understanding tokenises and tags: 'red' -> colour filter, 'kurta' -> category Women/Ethnic/Kurtas (with a confidence score), 'under 1000' -> price < 1000; leftover text is empty.
  3. 3High-confidence tags become hard filters; low-confidence ones become boosts, so a wrong guess does not wipe out results.
  4. 4Search service sends one Elasticsearch query: bool filter (category, colour, price, in_stock), BM25 on title/brand/attributes with field boosts, function_score with popularity; it also asks for facet aggregations (brands, sizes, price buckets).
  5. 5In parallel it queries the vector index with the query embedding, limited to the same category, for semantic matches ("maroon" items tagged as red-family).
  6. 6Results are merged with reciprocal rank fusion into ~400 candidates; the LTR model re-ranks them using conversion rate, rating, price competitiveness, return rate, delivery speed to the user's pincode and personal features.
  7. 7Response returns 40 products, facets with counts, the applied interpretation ("Showing Kurtas in Red under Rs 1,000" with an option to undo) and a page token.

Price drops in a sale

  1. 1Pricing service updates sku_offers; CDC (Debezium) emits a change event to Kafka keyed by style_id.
  2. 2Indexer consumes, recomputes min_price and max_discount for the style and sends a partial update in an Elasticsearch bulk request (batched every ~1 s).
  3. 3The doc is searchable with the new price after the next index refresh (~1 s); end-to-end lag is under a minute.
  4. 4Product page and checkout always read the live price from the pricing service, so a short search lag never charges a wrong price.

7 Deep dives

Query understanding is half of relevance

Ecommerce queries are short and full of structure: brand, category, colour, size, price, gender. 'puma shoes for men size 9' is really brand=Puma, category=Shoes, gender=Men, size=9. If you send it as plain text to BM25, products whose description says 'not for men' or a Puma t-shirt might rank high. The query understanding service runs a pipeline: spelling correction (trained on query logs, careful with brand names like 'Mamaearth'), tokenisation, a dictionary and NER model that tags spans with attributes, a category classifier that predicts the top 1-3 categories with probabilities, and price/size parsers. Output is a structured query: filters for high-confidence tags, boosts for medium ones, and leftover free text for BM25. Show the interpretation to the user with an easy undo. Measure the classifier on precision, because a wrong hard filter causes zero results. For Hinglish and regional variations ('chappal', 'jutti'), mine synonyms from logs where different queries end in the same purchases.

Hybrid retrieval and learning-to-rank

Lexical BM25 is precise for brand names, model numbers ('iphone 15 128gb') and exact attributes, but fails on vocabulary mismatch ('sofa for small room' vs 'compact 2 seater'). Dense retrieval with embeddings handles meaning but can miss exact model numbers. Use both: run BM25 and an ANN query (HNSW, in OpenSearch/Elasticsearch k-NN or a vector DB) with the same filters, then fuse with reciprocal rank fusion. The re-ranker is a learning-to-rank model (LambdaMART or a small neural model) over ~400 candidates. Features: text match scores per field, category match probability, product CTR and conversion over 7/30 days (smoothed for new items), rating and count, return rate, price vs category median, discount, delivery days to the user's pincode, seller quality and personal affinity (brand, size, price band). Labels: graded from logs (order > add-to-cart > click > skip), with position-bias correction. Offline metric: NDCG@10 on held-out logs; online: A/B on search conversion and revenue per search. Add a rule layer for sponsored products in fixed, labelled slots.

Keeping the index fresh without hurting latency

The catalogue has three update speeds. Product content (title, attributes, images) changes rarely. Price changes often, especially in sales. Stock changes with every order. All flow from the source databases via CDC into Kafka, partitioned by style_id so updates for one product stay in order. Indexers batch partial updates into bulk requests every second; Elasticsearch makes them searchable after the refresh interval. During peak, raise refresh_interval to 5-10 s and coalesce repeated updates for the same style in the indexer, because 100 stock decrements in a minute only need one write. For stock, search only needs 'in stock or not' per size, not the exact count, so the indexer writes only when a size flips between in-stock and out-of-stock. Schema changes or new analysers require a full rebuild: build a new index from a snapshot of the catalogue while replaying Kafka from the snapshot offset, validate it, then atomically switch an alias. Never reindex in place.

Facets and the variant problem

A kurta style comes in 5 sizes and 3 colours, each a SKU with its own stock and sometimes its own price. If you index one document per SKU, results show the same kurta 15 times and facet counts are inflated. If you index one per style, filtering 'size M in stock' needs the per-size stock inside the style document. Common approach: one document per style (or per style-colour, since colour changes the image) with arrays like sizes_in_stock: [S, M, L] and min_price. Facet counts come from terms aggregations on keyword fields; they count styles, matching what the user sees. Nested documents are exact but slower; flattened arrays are fast and good enough when you design the fields carefully. Facet aggregations on high-cardinality fields like brand (100k brands) are expensive, so limit to top N buckets and cache facet results for popular category pages.

8 Trade-offs

Elasticsearch/OpenSearch vs Solr vs custom engine

Elasticsearch/OpenSearch give text search, aggregations, kNN and operational tooling; most Indian e-commerce players started there. A custom engine (like Amazon's) makes sense only at very large scale with a dedicated team. Pick Elasticsearch and explain how you would shard it.

Hard filters vs boosts from query understanding

Hard filters give clean results but a wrong classification gives zero results. Boosts are safe but noisy. Use confidence thresholds, and if a filtered query returns too few results, retry with the filter turned into a boost.

Exact stock in index vs check at serve time

Writing every stock change into the index is expensive. Index only in-stock flags per size and verify exact availability on the product page and at checkout.

Personalisation in search

Light personalisation (size, price band, gender, preferred brands) as re-ranking features helps conversion. Heavy personalisation makes results hard to debug and can feel creepy; keep the query intent primary.

9 Bottlenecks

Sale-day QPS spike

10x traffic in the first hour. Pre-scale data nodes and replicas days before (Elasticsearch does not scale in minutes), cache results for the top 10k queries and category pages for 30-60 s, and switch the re-ranker to a cheaper model if latency rises.

Indexing pressure during sales

Millions of price/stock updates compete with search for CPU. Coalesce updates, increase refresh interval, use dedicated ingest/coordinating nodes and throttle bulk indexing when search p99 crosses a threshold.

Deep pagination

from/size beyond a few thousand results is costly. Cap pages (users rarely go past page 10) and use search_after for infinite scroll.

10 Failure handling

Re-ranker times out

Fall back to the first-stage Elasticsearch order (BM25 + popularity function score). Results are slightly worse but returned on time.

Bad synonym or classifier release

A wrong rule like 'apple' -> fruit can tank a category. Version query understanding dictionaries, roll out behind a flag, monitor zero-result rate and conversion per query segment, and roll back in minutes.

Elasticsearch node loss

Replicas take over; the cluster rebalances shards. Keep shard sizes small (10-20 GB) so recovery is quick, and run at most ~60-70% CPU at peak to absorb a node loss.

11 What impresses

  • Turning text into structured filters and showing the interpretation with an undo
  • Explaining why stock is indexed as in-stock flags, not exact counts
  • Handling the variant/facet counting problem explicitly
  • Falling back from hard filter to boost when results are too few
  • Talking about position bias in click logs and graded labels from orders
  • Pre-scaling search for sales because Elasticsearch scales slowly

12 Mistakes

  • Running search queries against the transactional database
  • Indexing one document per SKU and showing duplicates
  • Ignoring stock: showing products that cannot be bought
  • Re-indexing the whole catalogue for each price change
  • Ranking only by text relevance with no business or quality signals
  • No plan for zero-result queries

13 Follow-ups

Q1How would you support search by image ("find similar")?
Q2How do you rank a brand-new product with no clicks yet?
Q3How would you show delivery-by-tomorrow as a filter for each user's pincode?
Q4How do you evaluate a new ranking model before an A/B test?
Q5How would you handle sponsored products without hurting relevance?
Q6How do you support vernacular queries in Hindi or Tamil script?

Practice questions

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