◐ system-design/design-yelp.md
40. Design Yelp / Geo-Search
Tests: spatial indexing, faceted search, ranking, photos, reviews, abuse handling.
~2 min read·updated 5/29/2026
40. Design Yelp / Geo-Search
Tests: spatial indexing, faceted search, ranking, photos, reviews, abuse handling.
40.1 Requirements
Functional
- Search businesses near a location.
- Filter by category, price, rating, hours.
- Browse business detail (photos, reviews, hours).
- Write reviews and rate.
- Photos.
Non-functional
- 200M businesses globally; 200M MAU.
- P95 search < 200 ms.
- Reviews must persist forever.
40.2 Architecture
[ Client ]──→[ API gateway ]──→[ Search svc ]──→[ Geo + text index (ES with geo + facets) ]
↓
[ Business svc ]──→[ Postgres / DynamoDB ]
[ Reviews svc ]──→[ Reviews DB sharded by business_id ]
[ Photos svc ]──→[ Object store + CDN ]
[ Recommendations / personalization ]
40.3 Geo-search
(See chapter 25 / 35 for spatial indexing.)
Options:
- R-tree (PostGIS): great in single DB.
- Geohash: prefix-based; works in any KV.
- S2 / H3: Google / Uber hex/cell hierarchies.
- Elasticsearch geo_distance / geo_bounding_box queries: built-in.
For Yelp's read-heavy workload: Elasticsearch with geo + text + facets is the standard.
Indexed fields:
name,categories,description(text)location(geo_point)price_level(numeric)rating(float)is_open_now(computed)
Query:
{
"bool": {
"must": [
{ "match": { "name": "pizza" } }
],
"filter": [
{ "geo_distance": { "distance": "5km", "location": {lat, lon} } },
{ "term": { "categories": "Italian" } },
{ "range": { "price_level": { "lte": 2 } } }
]
},
"sort": ["_score", { "rating": "desc" }]
}
40.4 Business data
businesses (
id, name, address, location (lat,lon), categories[],
hours, price_level, rating_avg, review_count, photo_urls[],
owner_id, created_at, ...
)
Postgres for OLTP / source of truth; mirror to ES for search.
40.5 Reviews
reviews (
id, business_id, user_id, rating, text, photos[], created_at,
helpful_count, status (visible/hidden)
)
Sharded by business_id (queries are usually "show reviews for this business").
Rating aggregation:
- Stream from Kafka (
ReviewCreated). - Update materialized aggregates: per-business rating_avg, count.
- Stored in business record (denormalized).
40.6 Photos
(See chapter 37 / 34.) Object storage + CDN. Multiple resolutions for grid views.
40.7 Search ranking
Beyond geo + text match:
- Quality: rating, review count, response time, hours-open-now.
- Distance (closer is better, but small weight after a threshold).
- Personalization: user's history, similar users.
- Sponsored: paid placement (clearly labeled).
- Recency: new businesses get a boost.
Final score = weighted combination + ML re-rank for top candidates.
40.8 Categories
Hierarchical taxonomy: "Restaurants > Italian > Pizza."
Indexed as multi-value field; query uses terms filter with sub-categories.
40.9 "Open now" computation
Business hours stored per day. "Open now" = check current time against day's hours.
Computed in real-time at query time (cheap) or precomputed every minute and indexed.
40.10 Reviews moderation
Spam, fake reviews, abuse:
- Heuristic flags (spam keywords, bursts from one IP, too positive on a new business).
- ML classifier.
- Manual review queue for borderline cases.
- Yelp's notorious "review filter" hides reviews algorithmically.
Deceptive practices: review-bombing detected by sudden spikes; rate-gate new reviews.
40.11 Personalization
- User's previous searches, visits, reviews → preferences.
- Collaborative filtering: "people who liked X also liked Y."
- Diversity: don't show 10 pizza places when "Italian" was searched (mix in pasta, etc.).
40.12 Caching
- Search results for popular queries cached (key: query + location grid + filters).
- Business details cached (popular places).
- Photos via CDN.
Hot cells (Times Square at noon): cache aggressively; pre-warm.
40.13 Sketch
[Client]──search params──→[Search svc]──→[ES (geo + text + facets)]
│
[Business svc]──→[Postgres]
│
[Reviews svc]──→[Reviews DB]
│
[Photos]──→[CDN/object store]
Write paths:
[Reviews svc]──Kafka(ReviewCreated)──→[Aggregator (Flink)]──→update business.rating_avg
[Business svc]──Kafka(BusinessUpdated)──→[Indexer]──→ES
40.14 What an interviewer wants
- Geo-search via ES geo queries (or H3/S2 if Uber-style).
- Business denormalized (rating_avg, review_count) for fast ranking.
- Faceted filters as part of search query.
- Reviews sharded by business_id.
- Async aggregation for ratings.
- Photos in object storage + CDN.
Key takeaways
- Elasticsearch with geo + text + facets is the standard search backend.
- Denormalize hot fields (rating_avg) onto business records.
- Async aggregation pipelines update denormalized fields.
- Spam / fake review handling is essential.
- Personalization on top of popularity for ranking.
// 2 views