# Low Level Design: Nearby Search Service

Source: https://www.techinterview.org/post/3233471000/lld-nearby-search/
Updated: 2026-04-17 · techinterview.org

## 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](https://www.techinterview.org/post/3233460273/uber-interview-guide-2026-dispatch-systems-geospatial-algorithms-and-marketplace-engineering/)

See also: [Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence](https://www.techinterview.org/post/3233460274/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](https://www.techinterview.org/post/3233460306/lyft-interview-guide-2026-rideshare-engineering-real-time-dispatch-and-safety-systems/)
