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)]

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