# Price Alert System Low-Level Design

Source: https://www.techinterview.org/post/3233467686/lld-price-alert/
Updated: 2026-04-17 · techinterview.org

## 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](https://www.techinterview.org/post/3233460275/scale-ai-interview-guide-2026-data-infrastructure-rlhf-pipelines-and-ml-engineering/).

Price alert and e-commerce notification systems are discussed in [Shopify system design interview preparation](https://www.techinterview.org/post/3233459924/shopify-interview/).

Price tracking and alert notification system design is covered in [Google system design interview guide](https://www.techinterview.org/post/3233460274/netflix-interview-guide-2026-streaming-architecture-recommendation-systems-and-engineering-excellence/).
