Price Alert System Low-Level Design

What is a Price Alert System?

A price alert system notifies users when a tracked item’s price drops below their target threshold. Used by Amazon, Google Shopping, Honey, and Camelcamelcamel. The core challenge: millions of users have set alerts on millions of products. When any product’s price changes, efficiently find and notify only the users whose threshold has been crossed — without scanning all alerts.

Requirements

  • Users set price alerts: “notify me when product X drops below $Y”
  • Process 100K product price changes per hour
  • Evaluate millions of alerts per price change in <5 seconds
  • Send notifications via email, push, and in-app
  • Deduplicate: don’t notify the same user twice for the same alert until the price recovers above their threshold
  • Alert management: list, pause, delete alerts

Data Model

PriceAlert(
    alert_id    UUID PRIMARY KEY,
    user_id     UUID NOT NULL,
    product_id  UUID NOT NULL,
    target_price DECIMAL(10,2) NOT NULL,   -- notify when price <= this
    status      ENUM(ACTIVE, TRIGGERED, PAUSED, DELETED),
    created_at  TIMESTAMPTZ,
    last_triggered_at TIMESTAMPTZ,
    INDEX (product_id, target_price) -- critical for efficient evaluation
)

ProductPrice(
    product_id  UUID PRIMARY KEY,
    current_price DECIMAL(10,2),
    previous_price DECIMAL(10,2),
    updated_at  TIMESTAMPTZ
)

AlertNotification(
    notification_id UUID PRIMARY KEY,
    alert_id    UUID,
    user_id     UUID,
    product_id  UUID,
    triggered_price DECIMAL(10,2),
    sent_via    VARCHAR[],  -- ['email', 'push']
    sent_at     TIMESTAMPTZ
)

Efficient Alert Evaluation

When product X’s price changes to $45: find all ACTIVE alerts for product X with target_price >= $45 (i.e., threshold crossed).

def evaluate_alerts(product_id, new_price, old_price):
    if new_price >= old_price:
        return  # price went up — no alerts to trigger

    # Find all alerts whose threshold is now crossed
    alerts = db.query('''
        SELECT * FROM PriceAlert
        WHERE product_id = :pid
          AND status = 'ACTIVE'
          AND target_price >= :new_price
        ORDER BY target_price DESC
    ''', pid=product_id, new_price=new_price)

    for alert in alerts:
        trigger_alert(alert, new_price)

def trigger_alert(alert, triggered_price):
    # Mark as triggered (deduplication — won't fire again until price recovers)
    db.update(PriceAlert, alert.id, status='TRIGGERED',
              last_triggered_at=now())

    # Queue notification
    notification_queue.enqueue({
        'user_id': alert.user_id,
        'alert_id': alert.alert_id,
        'product_id': alert.product_id,
        'triggered_price': triggered_price,
        'target_price': alert.target_price
    })

Price Recovery Reset

Once triggered, the alert should re-arm when the price recovers above the threshold:

def on_price_change(product_id, new_price, old_price):
    evaluate_alerts(product_id, new_price, old_price)

    # Re-arm triggered alerts if price recovered above threshold
    if new_price > old_price:
        db.execute('''
            UPDATE PriceAlert SET status='ACTIVE'
            WHERE product_id=:pid
              AND status='TRIGGERED'
              AND target_price < :new_price
        ''', pid=product_id, new_price=new_price)

Handling Large Products (Hot Items)

For a popular product with 500K active alerts (e.g., iPhone), evaluating all on every price change is expensive. Optimize:

# Redis sorted set: product → alerts indexed by target_price
# Key: alerts:{product_id}
# Score: target_price
# Member: alert_id

# On price drop to $45: find all alerts with target_price >= 45
triggered_alerts = redis.zrangebyscore(f'alerts:{product_id}', 45, '+inf')

# Process in batches of 1000; publish to Kafka for async notification
for batch in chunks(triggered_alerts, 1000):
    kafka.produce('alert-triggers', {'product_id': product_id,
                                     'price': 45.0,
                                     'alert_ids': batch})

Key Design Decisions

  • Index on (product_id, target_price) — enables fast range query for alerts whose threshold was crossed
  • TRIGGERED status for deduplication — prevents repeat notifications until price recovers above threshold
  • Redis sorted set for hot products — O(log n + k) query for alerts with target_price >= new_price; faster than DB for products with many alerts
  • Async notification via queue — decouples alert evaluation (fast, synchronous) from notification delivery (slower, may fail)
  • Price recovery re-arms alerts — users don’t need to manually reset; alert re-triggers on the next price drop

Price alert and product tracking system design is discussed in Amazon system design interview questions.

Price alert and e-commerce notification systems are discussed in Shopify system design interview preparation.

Price tracking and alert notification system design is covered in Google system design interview guide.

Scroll to Top