Design problems
E-commerce & marketplacesMediumSDE-2 25 min

Design a price tracker and alerts

Asked at
AmazonFlipkartGoogleMyntraNykaa

The interviewer asks

“Design a service like Keepa or a price-drop tracker for Indian shoppers: users paste a product link from Amazon, Flipkart or other stores, see the price history, and get notified when the price drops below their target. How do you collect prices at scale and fire alerts quickly and only once?”

What a strong answer looks like

A strong answer separates three parts: collecting prices, storing history, and matching alerts. Collection is a scheduler that decides how often to re-check each product (popular and volatile products every 15-30 minutes, the long tail once a day), using official affiliate/partner APIs where possible and a polite, rate-limited fetcher otherwise. Each observation goes into a time-series store, but only price changes are written, which cuts storage by more than 10x. Alert matching must not scan all alerts on every price change: index alerts per product sorted by target price, so a new price P triggers exactly the alerts with target >= P that have not fired. Notifications are de-duplicated and rate-limited per user. Deep dives: crawl scheduling and politeness, the alert index, and data quality (fake drops, out-of-stock, seller variants). Mention sale events like Big Billion Days and Great Indian Festival, when prices change every few minutes.

Concepts to mention

tiered / adaptive crawl schedulingper-store rate limiting and politenesswrite only price changestime-series store for historyalert index by product and target priceconditional update to fire alerts oncenotification dedupe and per-user limitscanonical product identity (SKU + variant + seller)outlier rejection for bad prices

1 Requirements

Functional

  • User adds a product by URL; the system normalises it to a canonical product (store + product ID + variant).
  • Show price history chart (lowest, highest, average over 30/90/365 days).
  • User sets a target price or 'any drop of X%' alert; can also set back-in-stock alerts.
  • Notify by push, email or WhatsApp when the condition is met, once per alert unless the user re-arms it.
  • Browser extension shows history on the store's product page.
  • Deals page listing the biggest real drops right now.

Non-functional

  • Alerts sent within 15 minutes of the price change for popular products, within a few hours for the long tail.
  • Price history reads under 200 ms p99 (extension calls it on every product page view).
  • Track ~50M products and ~20M active alerts.
  • Respect store terms and rate limits; never overload a store.
  • 99.9% availability for reads; collection can lag briefly without data loss.

Out of scope (say it out loud)

  • Buying on the user's behalf.
  • Bypassing anti-bot protections or scraping in breach of terms (use partner APIs where available).
  • Price prediction models (mention only).

2 Estimates

Tracked products~50MProducts added by users plus the top sellers in each category.
Price checks~175M/day ≈ 2,000/s5M hot products every 30 min = 240M/day is too much; tier it: 2M hot × 48 + 10M warm × 4 + 38M cold × 1 ≈ 96M + 40M + 38M ≈ 175M/day.
Price changes written~10M/dayMost checks see no change; assume ~6% change → ~10M rows/day.
History storage~1 TB/year10M changes/day × 365 × ~30 bytes compressed ≈ 110 GB raw; with indexes and replicas ~1 TB.
Active alerts~20M~5M users × 4 alerts each.
Extension reads~3k/s peak~2M extension users × 50 product views/day = 100M/day ≈ 1.2k/s; sale days ×3.
Alerts fired~1M/day, sale days ~10M/day~5% of alerts trigger on a normal day; sale days most watched products drop.

3 API

EndpointWhat it does
POST/v1/products:resolveResolve a store URL to a canonical product_id; starts tracking if new.
GET/v1/products/{id}/history?range=90dPrice points and summary (min/max/avg) for the chart.
POST/v1/alertsCreate alert {product_id, type: below|drop_pct|in_stock, target}.
DELETE/v1/alerts/{id}Remove an alert.
GET/v1/deals?category=&min_drop=20Biggest verified drops right now.
POST/internal/observationsFetcher reports {product_id, price, mrp, in_stock, seller, ts}.

4 Data model

productsPostgres
product_id (PK)storestore_skuvarianttitleurltier (hot|warm|cold)next_check_atlast_pricelast_change_at

next_check_at indexed for the scheduler; tier recomputed daily from watchers and volatility.

price_pointsTimescaleDB / ClickHouse
product_idtsprice_paisemrp_paisein_stockseller_id

Only changes are written. Partitioned by time, ordered by (product_id, ts) so one product's history is a range scan.

history_summaryRedis
hist:{product_id} → compressed last 365 days + min/max/avg

Serves the extension; updated on each change, TTL a few days for cold items.

alertsPostgres (sharded by product_id)
alert_iduser_idproduct_idtypetarget_paisestate (armed|fired|paused)fired_at

Index (product_id, state, target_paise DESC) so 'armed alerts with target >= P' is a range scan.

notificationsPostgres / DynamoDB
notification_idalert_iduser_idpricechannelsent_atdedupe_key

dedupe_key = alert_id + price-change id stops double sends.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAEXTERNALhistoryalertsdue productsenqueuerate-limitedobservationwrite if changedarmed alerts >= PAlertFiredApp / browserextensionAPI gateway + CDNCrawl schedulerProduct + alert APIFetch queue (perstore)Fetchers / partnerAPIChange detector +matcherNotification workerPostgres (products,alerts)Time-series (pricepoints)Redis (history cache)Store sites / APIsFCM / email / WhatsApp
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Scheduled price check

  1. 1Scheduler queries products where next_check_at <= now in batches (index on next_check_at), using SKIP LOCKED so several scheduler instances do not pick the same rows.
  2. 2It enqueues fetch jobs into per-store queues; each store queue has a token-bucket rate limit (e.g. 20 req/s) shared by all fetchers.
  3. 3A fetcher calls the partner/affiliate API or fetches the page, parses price, MRP, stock and seller, and sends an observation.
  4. 4Change detector compares with last_price; if unchanged it only updates next_check_at.
  5. 5If changed, it writes a price point, updates the Redis summary and last_price, then runs alert matching.
  6. 6Next check time is set from the tier and recent volatility (a product that changed twice today is checked sooner).

Alert fires

  1. 1New price P = ₹18,499 for product X.
  2. 2Matcher runs: SELECT alerts WHERE product_id = X AND state = 'armed' AND target_paise >= P (index range scan).
  3. 3For each match it flips state to 'fired' with a conditional update (WHERE state = 'armed'), so concurrent matchers cannot fire twice.
  4. 4It emits AlertFired events with dedupe_key to the notification queue.
  5. 5Notification worker applies per-user limits (e.g. max 10 alerts/hour, bundled into a digest), sends via FCM/email/WhatsApp and records the send.

Extension shows history

  1. 1Extension sends the page URL; API maps it to product_id through a URL-normalisation cache.
  2. 2API returns the compressed history from Redis (cache hit > 95%); on miss it reads the time-series store and fills Redis.
  3. 3If the product is not tracked yet, it is created with tier 'warm' and an immediate check is queued.

7 Deep dives

Crawl scheduling: freshness within a budget

You cannot check 50M products every 15 minutes; that is 4.8B fetches a day. So treat fetches as a budget and spend it where value is highest. Value of checking a product depends on how many users watch it (alerts, extension views), how volatile its price is, and whether a sale is running. Compute a tier daily: hot (has many alerts or changed recently) every 15-30 minutes, warm every 4-6 hours, cold once a day. Adapt between checks: after a change, shorten the interval; after many unchanged checks, lengthen it (like an exponential backoff on freshness). Before a known sale (Big Billion Days, Great Indian Festival), raise frequency for watched products and pre-scale fetchers. Politeness is part of the design: per-store token buckets in Redis cap request rate, fetchers honour robots rules and back off on 429 or 503, and you prefer official APIs (Amazon's Product Advertising API, affiliate feeds) because they are stable, legal and cheaper than parsing HTML. The scheduler itself is simple: an indexed next_check_at column, batch claims with FOR UPDATE SKIP LOCKED, and per-store queues so one slow store cannot block the others.

Alert matching without scanning everything

The naive design scans all 20M alerts after each price change, or checks each alert on a timer. Both waste work. The key insight is that alerts are keyed by product: a price change for product X can only affect alerts on X. Store alerts sharded by product_id with an index on (product_id, state, target_paise). When the price becomes P, a single range query returns armed alerts with target >= P; usually a handful, rarely thousands for a popular phone. Percentage-drop alerts are stored as an absolute target computed when the alert is created (target = price_at_creation × (1 - pct)), so they use the same index. Back-in-stock alerts are a separate small list per product triggered on the in_stock false→true edge. Firing must be exactly once per arming: use a conditional update from armed to fired and a dedupe key on the notification. If the user wants repeated alerts, re-arm with a new lower target (e.g. notify again only if it drops another 5%) to avoid spamming during a sale where the price flips between two values every few minutes.

Data quality: real drops vs noise

Price pages are messy. The same product has many sellers and the buy-box seller changes; a variant (colour, storage) may have a different price; a 'deal price' may need a bank card; out-of-stock pages may show a stale or zero price; parsers break when the store changes its HTML. Bad data creates fake alerts and kills trust. Defences: canonicalise products by store SKU plus variant, not by URL; record seller and in_stock with each observation; ignore prices when the item is out of stock; reject observations that differ by more than, say, 70% from the median of the last 30 days unless confirmed by a second fetch a few minutes later; monitor parser success rate per store and page template, and alert when it falls. For the deals page, compare against the 90-day median rather than the inflated MRP, because many 'big discounts' are against an MRP nobody pays.

8 Trade-offs

Store only changes vs every observation

Writing only changes cuts storage and write load by more than 10x and makes history queries fast. You lose the record of when the price was last confirmed, so store last_checked_at on the product row to show 'checked 10 min ago'.

Push-based matching on change vs periodic alert scans

Matching on change is event-driven and costs work only when prices move. Periodic scans are simpler but slow and expensive at 20M alerts. Event-driven wins, with a nightly sweep as a safety net for missed events.

Time-series DB vs Postgres table

At 10M rows/day, a partitioned Postgres table works for a while. TimescaleDB or ClickHouse give compression (often 10x) and fast range scans by product and time, which matters once history is billions of rows.

Partner APIs vs page fetching

Official APIs are stable and compliant but have quotas and may not expose every field. Page fetching covers more stores but breaks often and must be rate-limited carefully. Use APIs first and fetch pages only within the store's rules.

9 Bottlenecks

Sale day surge

Prices change every few minutes and alerts fire 10x more. Notifications become the bottleneck: queue them, batch per user into digests, and scale notification workers; respect FCM and WhatsApp throughput limits.

Hot products with huge alert lists

A popular phone may have 200k alerts. One range query returning 200k rows is slow; process in pages, fan out to the notification queue in chunks, and keep the index tight.

Store rate limits cap freshness

When the per-store budget is the limit, the only lever is smarter prioritisation: spend fetches on watched and volatile products, and use extension page views as free freshness signals (the extension can report the price it sees, verified before use).

10 Failure handling

A store changes its page layout

Parser success rate drops; the monitor pauses that store's parser version so bad prices are not written, alerts on-call, and old prices stay in place. Products show 'last checked' honestly.

Notification provider is down

Alerts stay in the queue with retries and backoff; dedupe keys prevent duplicates when the provider recovers. Fall back to email if push fails for long.

Scheduler crashes mid-batch

Claimed rows have a lease (claimed_until); if a scheduler dies, another picks them up after the lease. Jobs are idempotent because a repeated fetch only writes a point if the price changed.

11 What impresses

  • Treats fetches as a budget and shows the math for why everything cannot be checked every 15 minutes.
  • Indexes alerts by product and target so matching is a range scan, not a full scan.
  • Stores percentage alerts as absolute targets so one index serves both types.
  • Talks about seller, variant and out-of-stock noise and how to avoid fake alerts.
  • Prefers official partner APIs and respects store limits.
  • Compares deals against the 90-day median instead of MRP.

12 Mistakes

  • Scanning all alerts on every price update or on a timer.
  • Fetching every product at the same fixed interval.
  • Writing every observation, blowing up storage without benefit.
  • Firing the same alert repeatedly as a price flips during a sale.
  • Treating the URL as the product identity, so variants and tracking parameters create duplicates.
  • Ignoring store rate limits and getting blocked.

13 Follow-ups

Q1How would you predict whether a price will drop in the next week?
Q2How do you support a new store with a different page structure quickly?
Q3How would you use the browser extension's own page views to improve freshness safely?
Q4How do you keep the deals page from showing fake discounts?
Q5What changes if you must send alerts within 1 minute for 1M hot products?

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