Price Alert
Design a price alert system: inverted-index matching against 50M alerts, a cooldown state machine that eliminates notification storms, and idempotent fan-out to email, push, and SMS.
What is a price alert system?
A price alert system watches prices across products and financial instruments and notifies users when a target threshold is crossed. The apparent simplicity hides a matching problem: when a price update arrives for one of millions of tracked items, which of the millions of active alerts does it trigger? A naive scan collapses immediately at scale. The interesting engineering is an inverted index that turns a price update into a list of triggered alerts in microseconds, plus idempotent multi-channel delivery that fires exactly once even when prices bounce around the threshold. It is a strong interview question because it combines an inverted-index data structure problem, event-driven pipeline design, idempotent notification delivery, and a nuanced alert state machine, testing four distinct skills together.
Functional Requirements
Core Requirements
- Users can create a price alert on any tracked product or stock ticker, specifying a target price and direction (price drops to or below target, or rises to or above target).
- Users receive a notification when the current price crosses their threshold.
- Users can choose which channels receive the notification: email, push notification, or SMS.
- Users can view, pause, and delete their active alerts.
Below the Line (out of scope)
- Order execution or automated trading once the alert fires.
- Complex multi-condition alerts (for example, "alert if price drops AND trading volume exceeds X").
- Price history charts, trend analysis, and historical price data.
- Social features such as sharing alerts or following other users' watchlists.
Order execution belongs to a trading system that sits downstream of the alert. Linking the two would couple notification delivery to financial settlement, which carries entirely different reliability and compliance requirements. To add it, the alert would publish an event to a trading engine asynchronously; the alert system itself stays stateless with respect to the trade.
Complex conditional alerts could be built by adding a rule evaluation layer in front of the current threshold check. Each price update would be run through a small expression evaluator. The data model would store a rule AST instead of a single target_price scalar. Deliberately deferred because it doesn't change the core ingestion and matching pipeline.
Price history is below the line because it doesn't change the ingestion or matching pipeline. To add it, capture every PriceUpdate event to a time-series DB (TimescaleDB or InfluxDB) on the write path. The alert matching pipeline reads only the latest price; the history reader would be a separate query path that doesn't touch the alert evaluation logic at all.
Social alert features don't interact with the evaluation pipeline. They'd live in a separate follow-graph service that aggregates public alert activity. The alert system would publish anonymized trigger events to a feed topic, and the social service would consume them independently. The matching and notification pipeline wouldn't change.
The hardest part in scope: When a price update for a single item arrives, the system must instantly identify which of potentially thousands of active alerts for that item to fire, then deliver each notification exactly once even if the price oscillates around the threshold for minutes.
Non-Functional Requirements
Core Requirements
- Notification latency: Alert fires within 60 seconds of a triggering price update. Most users accept near-real-time; sub-second is not required and would over-engineer the ingestion pipeline.
- Throughput: Ingest up to 50,000 price updates per second across all tracked items during peak trading hours. Supported without batching at the ingestion layer.
- Scale: Support 50 million active alerts across 5 million tracked items.
- Availability: 99.9% uptime for alert creation and management. A brief spike that delays notifications by a few minutes is tolerable; silently missing a triggered alert is not.
- Exactly-once delivery: Each alert fires at most once per trigger event. Duplicate notifications erode user trust faster than latency does.
Below the Line
- Sub-second notification delivery (would require a dedicated low-latency push path; unnecessary for this use case).
- Real-time price data fidelity for high-frequency trading (our 60-second SLA allows seconds of price lag from the feed).
Read/write ratio: Price updates arrive far more often than users create or modify alerts. For every alert creation, we see roughly 1,000 price updates across the platform. The evaluation pipeline is the hot path. This 1000:1 ratio means write throughput for alert management is modest; the system must be optimized for high-volume, low-latency price evaluation, not for alert CRUD. Every architectural decision downstream traces back to this skew.
Core Entities
- Alert: A user's price threshold for a specific item. Captures the target price, direction (lte or gte), preferred notification channels, and current firing state (active, triggered, paused).
- Item: A tracked product from a retailer catalog or a financial instrument (stock, ETF, crypto). The unit of price updates.
- PriceUpdate: The current price of an item at a point in time, sourced from a price feed. Ephemeral; only the latest price for each item needs to be retained outside the event log.
- Notification: A record of a dispatched notification message. Serves as the idempotency log; before firing, the system checks this table to prevent duplicate sends for the same alert trigger event.
- User: The account that owns alerts and holds channel credentials (email address, push token, phone number).
The primary relationship is User β Alert β Item. An item can have thousands of alerts from different users. Full schema and indexing decisions are deferred to the deep dives.
API Design
One endpoint per core functional requirement, grouped by the requirement it satisfies.
FR 1 and FR 3: Create a price alert with channel preferences:
POST /alerts
Authorization: Bearer {token}
Body: {
item_id: "AAPL",
target_price: 150.00,
direction: "lte",
channels: ["push", "email"]
}
Response 201: {
alert_id: "alrt_8fk2x",
item_id: "AAPL",
target_price: 150.00,
direction: "lte",
channels: ["push", "email"],
status: "active",
created_at: "2026-03-29T10:00:00Z"
}
direction: "lte" means "alert me when price falls to or below target". Using an explicit direction field (rather than inferring it from whether the current price is above or below target) makes the intent unambiguous and allows both "price drop" and "price recovery" alerts to coexist on the same item.
FR 4: List alerts (paginated):
GET /alerts?status=active&cursor=alrt_8fk2x&limit=25
Response 200: {
alerts: [...],
next_cursor: "alrt_9gm3y"
}
Cursor-based pagination over keyset on alert_id (ordered by created_at). Offset pagination collapses when rows are inserted between pages; cursor pagination is stable.
FR 4: Update an alert (pause, resume, modify target):
PATCH /alerts/{alert_id}
Body: { status: "paused" } // or: { target_price: 145.00 }
Response 200: { alert_id, status, target_price, ... }
PATCH over PUT because users typically update one field at a time (pause/resume or adjust threshold). A full PUT would require the client to re-send all fields.
FR 4: Delete an alert:
DELETE /alerts/{alert_id}
Response 204: (empty)
Hard delete. The alert is removed from the matching index immediately so no further notifications are evaluated. Notification history is preserved under the Notification entity.
Internal (price ingestion, write-only, not user-facing):
POST /internal/prices
Body: {
updates: [
{ item_id: "AAPL", price: 149.50, timestamp: "2026-03-29T10:01:33Z", source: "nasdaq_feed" },
{ item_id: "NVDA", price: 800.00, timestamp: "2026-03-29T10:01:33Z", source: "nasdaq_feed" }
]
}
Response 202: { accepted: 2 }
202 Accepted is correct here: the system acknowledges receipt but alert evaluation is asynchronous. The source feed gets a fast acknowledgment without waiting for match evaluation and notification dispatch. In practice this endpoint is an internal Kafka publish, not an HTTP call; showing it as HTTP makes the contract explicit for the interview.
High-Level Design
1. Users can create and manage price alerts
The write path: a user submits an alert, it lands in persistent storage, and the system is ready to match incoming price updates against it.
Components:
- Client: Web or mobile app sending POST /alerts and PATCH/DELETE requests.
- Alert Service: Validates the request, persists the alert, and returns the
alert_id. - Alert DB (PostgreSQL): Primary store for alert records. Queried for management operations (list, update, delete).
Request walkthrough:
- Client sends
POST /alertswith item_id, target_price, direction, and channels. - Alert Service validates that item_id exists and target_price is a positive number.
- Alert Service inserts the alert row into Alert DB with
status = active. - Alert Service returns
{ alert_id, status }to the client.
This covers alert creation and management. Price evaluation hasn't started yet; the DB is purely a record store at this point.
2. When a price crosses a threshold, fire the matching alerts
This is the core matching problem. Price updates arrive for thousands of items per second, and the system needs to identify which active alerts to fire for each update. I'd spend the bulk of an interview on this section, because the naive approach fails in a very instructive way.
Phase 1: Naive scan
The simplest idea is to query the database on every price update: find all active alerts for this item where the threshold is breached.
Components:
- Price Ingestion Worker: Consumes a price feed and for each update runs a DB query.
- Alert DB: Scanned for matching alerts on each price change.
Request walkthrough:
- Price feed publishes a new price for item
AAPL: $149.50. - Ingestion Worker runs:
SELECT * FROM alerts WHERE item_id = 'AAPL' AND status = 'active' AND direction = 'lte' AND target_price >= 149.50. - For each matched alert, insert a notification job into a queue.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.