Design ride-hailing and price alert systems
SnapchatSoftware Engineer · OnsiteLast updated
This question evaluates a candidate's competency in large-scale system design, including distributed architecture, real-time data processing, geo-spatial indexing and low-latency matching for ride-hailing, plus scalable ingestion, time-series storage, rule evaluation, and reliable notification pipelines for price-tracking, within the System Design domain. It is commonly asked to assess architectural trade-offs around scalability, consistency, availability, fault tolerance and operational concerns, testing practical application of architectural patterns alongside conceptual understanding of data modeling and real-time constraints.
During a system design interview, you are asked two related but separate design questions. Treat each as an independent end-to-end design; you will be evaluated on architecture, data modeling, and how you reason about scale, consistency, and failure.
Constraints & Assumptions
These apply to both parts unless a part states otherwise:
- Scale: Millions of monthly active users; design for horizontal growth, not a fixed peak.
- Multi-region: Users and data span multiple geographic regions.
- Mobile-first clients: HTTPS for request/response; long-lived connections (WebSocket / gRPC stream / MQTT) available when the app is foregrounded.
- Latency targets: Part 1 driver-matching results $p95 < 200\text{–}300,\text{ms}$ after a ride request; Part 2 alerts delivered within a few minutes of a qualifying price change.
- Availability: Target high availability (assume $\geq 99.9%$) with graceful degradation rather than hard outages.
- Money is special: Financial state (fares, charges) requires strong consistency; locations, ETAs, and price freshness can tolerate eventual consistency.
Clarifying Questions to Ask
Scope the whole problem before designing either part:
- Is this a single global deployment, or independent per-region stacks with a thin global layer (auth, payments)?
- What are the read/write ratios and absolute throughput targets (e.g., location updates/sec in Part 1, price refreshes/sec in Part 2)?
- For Part 1, can drivers serve multiple regions, and do we control the routing/ETA/map provider or integrate a third party?
- For Part 2, do we crawl sites ourselves, use partner APIs/feeds, or both — and what are the politeness/rate constraints?
- What notification channels are in scope (push, email, SMS, in-app)?
- What are the data-retention and analytics requirements (how long to keep trips / price history)?
Part 1: Design a ride-hailing service (similar to Uber)
Design the backend for a global, mobile-first ride-hailing platform that connects riders and drivers. The system must support:
- Real-time location updates from drivers (and riders) on the order of every few seconds.
- Matching a rider to nearby eligible drivers with low latency (the $p95 < 200\text{–}300,\text{ms}$ target above).
- Trip lifecycle management: request, accept, start, end, cancel.
- Basic surge pricing driven by local supply and demand.
- Durable persistence of trip and user data for billing and analytics.
- High availability and fault tolerance across regions.
Outline the high-level architecture, the key services and their data models, and how you would handle scalability, consistency, and fault tolerance. In particular, address: (a) how you store and index geo-locations for efficient nearby-driver queries, (b) how you keep driver locations fresh, and (c) how riders receive timely updates (driver ETA, trip status).
What This Part Should Cover
- Service decomposition with the right datastore per concern: transactional store (e.g., an RDBMS) for trips/users/payments; in-memory/geo-indexed store for live locations; an event bus connecting them.
- Geo-indexing & nearest-driver search: a concrete spatial scheme (geohash or S2), cell-based candidate gathering with ring expansion, and post-filtering by availability/car-type/rating before ranking by ETA/distance.
- Dispatch correctness: avoiding double-assignment (reservation/locking), accept timeouts with retry/widen-radius, and idempotent trip creation under mobile retries.
- Scale & fault tolerance: geographic partitioning, sharding the location and trip planes, multi-AZ deployment, and explicit CAP trade-offs (strong consistency for money, availability for locations/ETAs/surge).
Part 2: Design a price tracking and alerting system
Design a backend that lets users watch product prices and receive alerts when conditions are met. It must support:
- Users registering products to watch (by URL or product ID).
- Users defining alert rules — e.g., "notify me when price drops below $X" or "notify me when the discount is at least $Y%$."
- Periodic or near-real-time ingestion of current prices from external sources.
- Storing per-product price history.
- Sending notifications (email/push/SMS) when alert conditions are satisfied.
- Operating at scale for millions of tracked products and many more alert rules.
Describe the overall architecture, major components, data storage choices, and how you would handle: (a) efficiently crawling/ingesting prices without overloading external sites, (b) evaluating alert rules at scale with reasonable latency and cost, and (c) delivering reliable, de-duplicated notifications.
What This Part Should Cover
- Staged, queue-decoupled pipeline: ingestion → normalization/history → rule evaluation → notification, each independently scalable with backpressure.
- Polite, prioritized ingestion: per-product refresh frequency (popular = more often), partner APIs/feeds where available, robots.txt/rate-limit respect.
- Storage choices that fit access patterns: relational/KV for products and rules; a time-series/wide-column store for append-heavy price history; rules indexed by
product_id. - Rule evaluation at scale: sharding rules by product, in-memory/cached rule sets, correct handling of absolute-threshold vs percent-drop (baseline) rules.
- Idempotent, de-duplicated, rate-limited notifications with retry/dead-letter handling.
Hints
Hint 1
Where to start: Decompose by responsibility before drawing boxes: identity/profiles, a high-write **location** plane, a **matching/dispatch** path, the transactional **trip lifecycle**, **pricing/surge**, and a real-time **fan-out** plane to clients. These have very different consistency and throughput needs — don't put them in one service or one database.
Hint 2
Geo-indexing: The core query is "give me the K nearest *available* drivers to this point, fast." Map the globe to a discrete grid — **geohash** prefixes or **S2 cell IDs** at a resolution of a few hundred meters — so a lookup becomes "this cell plus its neighbors, expanding the ring until K candidates are found" instead of a full scan or a distance computation over every driver.
Hint 3
Freshness & fan-out: Treat the latest driver position as hot, ephemeral state (in-memory / Redis GEO), not a durable write per ping; expire stale entries with a TTL so matching never returns drivers who went offline. For live updates to clients, don't poll core services — publish trip/position events to a pub/sub bus and let a separate real-time gateway hold the WebSockets and fan out per-trip.
Hint 4
Where to start: Separate the pipeline into stages connected by a queue: **ingestion** (get the new price) → **normalization + history write** → **rule evaluation** (which watchers care?) → **notification**. Each stage scales independently on its own queue depth, and a queue between ingestion and evaluation gives you backpressure when a price refresh storm hits.
Hint 5
Evaluation fan-out: The expensive question is "for this new price, which of millions of rules fired?" Key rules by `product_id` so a `PriceUpdated` event only loads that product's rule set — turning a global scan into a per-product lookup. Percentage-drop rules need a *baseline* (first-seen / N-day reference), so think about where that baseline lives.
Hint 6
Reliable, de-duped delivery: A naive design re-alerts on every tick while the price sits below the threshold. Make delivery idempotent: record the last triggered state per rule (last price / event ID) and suppress repeats, and de-dup at the notification layer keyed by `(rule_id, trigger)` so a re-delivered queue message can't double-send.
Follow-up questions
- **Part 1**: How would you prevent two riders from being matched to the same driver simultaneously, and how do you recover a trip if the dispatch service crashes mid-assignment?
- **Part 1**: A whole region's location shard goes down. What does matching do in the meantime, and how do you rebuild hot state on recovery?
- **Part 2**: A flash sale changes prices on 100k watched products at once. How does your pipeline avoid a notification storm and stay within per-user rate limits?
- **Part 2**: How would you support richer rules (e.g., "lowest price in 90 days" or "price drop AND back in stock") without re-architecting evaluation?
- **Both**: How do you measure that you're meeting the latency SLOs (match $p95$, alert delivery), and what do you alert on when you're not?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.