Design product catalogue search (Myntra / Flipkart)
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
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
3 API
| Endpoint | What 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/events | Internal: 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 DBstyle_id (PK)titlebrandcategory_idattributes JSONdescriptionimages[]seller_idcreated_atWritten by the catalogue service. Search never reads it at query time.
sku_offersPostgres + Redis cachesku_id (PK)style_idsizecolourseller_idpricemrpstock_qtywarehouse_ids[]Fast-changing: price and stock. Owned by pricing and inventory services.
search_docElasticsearch / OpenSearchstyle_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 offlinephrase -> {categorybrandattributesynonym}e.g. 'kurta' -> category 'Women > Ethnic > Kurtas'Mined from query logs and catalogue attribute values; refreshed daily.
search_logsKafka -> data lakequeryinterpretationresults shown with positionsclicksadd-to-cartordersFeed for LTR training, synonym mining and zero-result analysis.
5 Architecture
Hover a box to highlight its connections.
6 Request flows
Search "red kurta under 1000"
- 1App calls GET /v1/search?q=red kurta under 1000; gateway forwards to the search service.
- 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.
- 3High-confidence tags become hard filters; low-confidence ones become boosts, so a wrong guess does not wipe out results.
- 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).
- 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).
- 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.
- 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
- 1Pricing service updates sku_offers; CDC (Debezium) emits a change event to Kafka keyed by style_id.
- 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).
- 3The doc is searchable with the new price after the next index refresh (~1 s); end-to-end lag is under a minute.
- 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
Lessons this design uses
Practice questions
- Design the search API for an e-commerce app: keyword search with filters (brand, price range, size), sorting and facet counts.
- How would you cache search and category listing results, like 'running shoes, under 3,000 rupees, sorted by popularity, page 2'?
- A marketplace sells phones, sarees and furniture. Each category has different attributes (RAM, fabric, dimensions). How do you model product attributes?
- Your search service fans each query out to 100 shards and waits for all of them. Each shard has p99 of 50 ms, but the overall p99 is 400 ms. Why, and how do you reduce it?
- Your product search uses SELECT * FROM products WHERE name ILIKE '%red shoe%' on 10 million rows and takes 4 seconds. Why is it slow and what would you build instead?
- Users search 'iphone' on your marketplace and the top results are iPhone covers and screen guards, not phones. How would you improve the ranking?
- Your search page shows filters like Brand (with counts), Price range and Rating. When a user selects 'Brand = Nike', the other brand counts disappear. How should faceted search work, and what does it cost?
- A seller-dashboard API lets sellers page through search results with ?page=N&size=50. Some scripts hit page 5,000 and the Elasticsearch cluster's CPU spikes. Explain why and redesign the pagination.
- During a festive sale, sellers change prices and stock for millions of products every minute, and your search results show stale prices and out-of-stock items. The Elasticsearch cluster is also struggling with the update load. How would you redesign this?
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.