Design Proximity Service
Nearby restaurants/places — geohash indexing, radius search, and ranking by distance + quality.
Interview tip Like Yelp nearby: GEORADIUS on Redis Geo or S2 cells. Clarify update frequency of business hours and duplicate listing merge.
① Functional requirements
- Search POIs within radius of point
- Filter by category, price, open_now
- Sort by distance, rating, or blended score
- Get POI details (hours, photos, reviews link)
- Business owner update listing
- Report closed or incorrect listing
② Non-functional requirements
- Search p99 < 100ms
- 50M POIs indexed
- 20K nearby QPS
- Index updates within 5 minutes
- 99.9% availability
③ Back-of-the-envelope scale
Assumptions
- 50M POIs × 200 bytes geo index ≈ 10GB geo index
- 20K QPS → Redis cluster or partitioned cell index
- Top 20 results per query — early terminate radius expansion
- Updates 1K/sec peak (hours changes)
④ High-level architecture
Proximity Service
Client apps
Nearby API
Geo index (Redis Geo / S2)
POI metadata DB
Ranking service
Geo index returns candidate IDs in radius. Fetch metadata batch from DB/cache. Ranker applies distance + rating + open_now boost.
⑤ Data flow & execution path
Nearby search
① lat,lng,radius→② Geo index query→③ Fetch POI batch→④ Rank + filter→⑤ Return top 20
Geohash cell covers query circle
Expand cells if <20 results
open_now from precomputed bitmap per cell
Cache popular downtown queries
Contrast with Uber live driver positions — POIs are mostly static; index rebuild batch vs streaming updates.
⑥ API & interfaces
| Endpoint / flow | Purpose | Notes |
|---|---|---|
| GET /nearby | Radius search | lat,lng,radius,category |
| GET /poi/{id} | POI details | cacheable |
| PUT /poi/{id} | Owner update | auth required |
| POST /poi | Add listing | moderation queue |
⑦ Data model & storage
POI: id, lat, lng, categories[], rating, hours_bitmap, geohash. Geo index: geohash → poi_ids[] or Redis GEOADD.
| Store | What | Why |
|---|---|---|
| Redis Geo | Live geo index | GEORADIUS |
| PostgreSQL | POI metadata | ACID updates |
| CDN | Photos | Static assets |
⑧ Deep dive — core components
Geohash cell expansion
Start with cell containing point. If results <20, query neighbor cells ring until radius covered or cap cells. Avoid full table scan.
open_now filter
Precompute open bitmap per POI for next 7 days in local TZ. At query, bitwise check — filter before rank to shrink candidate set.
⑨ Trade-offs & alternatives
| Decision | Option A | Option B | Pick when |
|---|---|---|---|
| Index | Redis Geo | S2 cells | Redis ops-simple; S2 better at poles/spans |
| Rank | Distance only | ML ranker | Distance cheap; ML for engagement |
| Updates | Realtime index | Batch nightly | Batch OK for static POIs |
| Cache | Query cache | No cache | Cache downtown lat/lng grids |
⑩ 45-minute interview script
- 0–5 min: Nearby + filters
- 5–10 min: POI scale
- 10–20 min: Geo index query
- 20–30 min: Ranking + open_now
- 30–38 min: Updates + moderation
⑪ Likely follow-up questions
| Question | Short answer |
|---|---|
| Multi-radius pagination? | Cursor with last distance + id; expand radius if page empty |
| Chain duplicate merge? | Canonical chain_id groups franchises; dedupe in ranker |
| Ads in results? | Reserve slots 3,7; blend sponsored score with relevance cap |
⑫ Revision checklist
- GEORADIUS / S2
- Cell expansion algorithm
- Batch metadata fetch
- Distance + rating rank
- open_now precompute
- Query result cache
- Owner update path
- Moderation for new POI