Low Level Design: Nearby Search Service

Entity Types

Nearby search must handle two fundamentally different entity classes:

  • Static entities — restaurants, venues, stores; position rarely changes
  • Dynamic entities — drivers, delivery workers, bikes; position updates every few seconds

Static Entity Index

Pre-built spatial index in PostgreSQL with PostGIS or Elasticsearch. Index is refreshed on entity update (not on every query). Suitable for millions of POIs with infrequent writes.

Dynamic Entity Index: Redis Geospatial

Redis provides native geospatial commands backed by a sorted set using geohash scores.

-- Add or update driver position
GEOADD drivers:{city} {lng} {lat} {driver_id}

-- Find drivers within 5 km, sorted by distance, max 20 results
GEORADIUS drivers:{city} {lng} {lat} 5 km ASC COUNT 20

-- Get distance between two members
GEODIST drivers:{city} driver_42 driver_99 km

GEORADIUS complexity: O(N + log M) where N = members in radius, M = total members.

Driver Tracking Pipeline

Mobile App (every 5s)
  -> POST /location {driver_id, lat, lng}
  -> Location Service
  -> GEOADD drivers:{city} lng lat driver_id
  -> Pub/Sub update for active ride tracking

Ranking Factors

Raw geo results are re-ranked by a composite score:

score = (1 / distance) * w1
      + rating * w2
      + availability * w3

-- Example weights
w1 = 0.5   -- distance dominates
w2 = 0.3   -- rating matters
w3 = 0.2   -- availability flag

Filtering

  • Category — cuisine type, vehicle type, etc.
  • Price level — 1-4 scale stored on entity
  • Open now — check hours table against current day/time
SELECT e.id, e.name, e.rating
FROM entities e
JOIN hours h ON h.entity_id = e.id
WHERE e.id IN (:geo_result_ids)
  AND e.category = :category
  AND h.day_of_week = :today
  AND :current_time BETWEEN h.open_time AND h.close_time
ORDER BY score DESC;

Entity Details Fetch

Geo query returns only IDs. Fetch full entity details from DB or cache by ID set in a single SELECT ... WHERE id IN (...) or Redis MGET.

Caching Strategy

  • Dynamic results (drivers): TTL 10 seconds — data changes rapidly
  • Static results (restaurants): TTL 60 seconds — stable data, cacheable longer
  • Cache key: nearby:{entity_type}:{geohash6}:{filters_hash}

Sharding by City

Use separate Redis instances per metropolitan area. City-level sharding keeps key spaces small and provides geographic fault isolation.

redis_pool = {
  'nyc': RedisClient('redis-nyc.internal'),
  'sf':  RedisClient('redis-sf.internal'),
  'la':  RedisClient('redis-la.internal'),
}
client = redis_pool[resolve_city(lat, lng)]

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How does Redis GEORADIUS work for nearby search?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Redis stores geo members in a sorted set using a geohash-derived score. GEORADIUS queries all members within a given radius in O(N + log M) time, where N is the number of results and M is the total member count. It supports sorting by distance and limiting result count.”
}
},
{
“@type”: “Question”,
“name”: “How do you handle real-time driver location updates at scale?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Mobile apps send location updates every 5 seconds to a location service, which writes to Redis using GEOADD. Shard the Redis keyspace by city (e.g. drivers:{city}) to keep each instance small. Use a pub/sub layer to push live position updates to active ride tracking consumers.”
}
},
{
“@type”: “Question”,
“name”: “What ranking factors matter in a nearby search result?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Distance is the primary factor, but a composite score combining distance, entity rating, and real-time availability produces better results. Assign weights to each factor and compute the score after the geo query returns candidate IDs. Filter by category, price level, and open status before ranking.”
}
},
{
“@type”: “Question”,
“name”: “What cache TTL should be used for nearby search results?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Dynamic entities like drivers should use a very short TTL (10 seconds) because positions change rapidly. Static entities like restaurants can use a longer TTL (60 seconds or more). Cache key should include the entity type, geohash cell, and a hash of active filters.”
}
}
]
}

See also: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Lyft Interview Guide 2026: Rideshare Engineering, Real-Time Dispatch, and Safety Systems

Scroll to Top