A ride-hailing backend needs to find every driver within 5 km of a pickup point using Redis. Which Redis commands would you use to store the driver positions and to run that proximity query, and what does the query return?
answer
- GEOADD = sorted set with geohash score
- longitude BEFORE latitude
- GEOSEARCH FROMLONLAT BYRADIUS ... ASC WITHDIST
- no GEODEL → ZREM
- results unsorted unless ASC/DESC
basics
~20 sGEOADD drivers <longitude> <latitude> <driverId> stores each position (longitude first). GEOSEARCH drivers FROMLONLAT <lon> <lat> BYRADIUS 5 km ASC WITHDIST returns the member names inside the circle, nearest first, with distances. Re-run GEOADD to move a driver.
solid answer
~50 sI'd keep one key per fleet region, e.g. `drivers:sf`. Writes are `GEOADD drivers:sf <lon> <lat> <driverId>` — note the argument order is **longitude then latitude**, which is the classic bug. A position update is just another `GEOADD` on the same member; it overwrites in place. The query is `GEOSEARCH drivers:sf FROMLONLAT <lon> <lat> BYRADIUS 5 km ASC COUNT 20 WITHCOORD WITHDIST`. It returns the member names inside the circle; `WITHDIST` adds the distance in the unit I asked for, `WITHCOORD` the stored coordinates, and `ASC` sorts nearest-first (results are unordered unless I ask). `COUNT` caps the reply. Supporting commands: `GEOPOS` to read a member's coordinates back, `GEODIST key a b km` for the distance between two stored members. There is no `GEODEL` — a driver going offline is removed with `ZREM`, because the index is a sorted set underneath.
code
text · 15 lines# write / move positions (longitude first!)
GEOADD drivers:sf -122.4194 37.7749 driver:17 -122.4089 37.7837 driver:23
# nearest drivers within 5 km, closest first, with distance
GEOSEARCH drivers:sf FROMLONLAT -122.4150 37.7800 BYRADIUS 5 km ASC COUNT 20 WITHDIST
# around an existing member instead of a raw point
GEOSEARCH drivers:sf FROMMEMBER driver:17 BYRADIUS 2 km ASC
# read back / measure
GEOPOS drivers:sf driver:17
GEODIST drivers:sf driver:17 driver:23 km
# driver logs off - there is no GEODEL
ZREM drivers:sf driver:17go deeper
Know GEOADD (longitude first), GEOSEARCH ... BYRADIUS with WITHDIST/ASC, GEOPOS and GEODIST, and that removal is ZREM.
Add the argument options — FROMMEMBER vs FROMLONLAT, BYBOX vs BYRADIUS, COUNT/ANY — and explain why results are unordered by default.
Talk about the write path (continuous GEOADD overwrites), staleness cleanup with a parallel last-seen sorted set, and using COUNT to bound reply size and latency.
Frame Redis geo as a cheap candidate-generation layer: it answers radius/box questions in memory, and a routing or ranking service turns candidates into product answers.
## What a Redis geospatial index actually is Redis does not have a separate "geo" data type. `GEOADD` writes into an ordinary **sorted set** — a collection of unique members, each with a numeric score, kept ordered by that score. The geo commands encode a longitude/latitude pair into a single 52-bit integer (a geohash) and store it as the member's score. Everything else — persistence, replication, memory accounting, `TTL` on the key — behaves exactly like a sorted set. ## Writing positions ``` GEOADD drivers:sf -122.4194 37.7749 driver:17 ``` The order is **longitude first, then latitude**. Most mapping APIs and most humans say "lat, lon", so swapping them is the single most common mistake; it will not error (both values are in range) — you just silently index a point somewhere else on the planet. `GEOADD` accepts many triples in one call and supports `NX` (only add new members), `XX` (only update existing), and `CH` (return the count of changed members rather than added ones). Updating a driver's position is simply another `GEOADD` for the same member: the member is unique, so the score is overwritten. There is no separate "move" command. Longitude must be in [-180, 180] and latitude in roughly [-85.05112878, 85.05112878]; out-of-range values are rejected with an error. ## Querying `GEOSEARCH` (Redis 6.2+) is the read command: ``` GEOSEARCH drivers:sf FROMLONLAT -122.4194 37.7749 BYRADIUS 5 km ASC COUNT 20 WITHDIST WITHCOORD ``` - **Centre**: `FROMLONLAT lon lat` for an arbitrary point, or `FROMMEMBER driver:17` to search around something already in the index. - **Shape**: `BYRADIUS r unit` for a circle, or `BYBOX width height unit` for an axis-aligned rectangle. Units are `m`, `km`, `mi`, `ft`. - **Ordering**: results are **not sorted by default**. `ASC` gives nearest-first, `DESC` farthest-first. - **Limiting**: `COUNT n` caps the reply; `COUNT n ANY` returns as soon as `n` matches are found, which is much cheaper but gives you an arbitrary `n`, not the nearest `n`. - **Extras**: `WITHDIST` (distance in the requested unit), `WITHCOORD` (the stored longitude/latitude), `WITHHASH` (the raw 52-bit score). Internally the search picks a geohash precision whose cells are at least as large as the search shape, then range-scans the nine cells around the centre (the containing cell plus its eight neighbours) and filters each candidate by exact distance. That is why the complexity is roughly O(N + log(M)) — logarithmic to locate the ranges, plus linear in the number of members sitting in the scanned area, not in the whole key. ## Reading and removing - `GEOPOS key member [member ...]` → the stored coordinates (approximate — see below), `nil` for unknown members. - `GEODIST key m1 m2 [unit]` → great-circle distance between two stored members; `nil` if either is missing. - `GEOHASH key member` → the standard 11-character geohash string, useful for interoperating with other systems. - **Deletion**: there is no `GEODEL`. Use `ZREM drivers:sf driver:17`. Likewise `ZCARD` counts members, `ZSCORE` shows the raw geohash, and `EXPIRE` sets a TTL on the whole index (never on one member). ## Precision caveats worth stating in an interview The coordinate is quantised into 52 bits, so `GEOPOS` returns a value very close to — but not exactly — what you wrote (worst case about 0.6 m off). `GEODIST` computes a great-circle distance on a sphere, so it is a straight-line "as the crow flies" number with a small percentage error, not a road or routing distance. If the product needs driving time, Redis gives you the cheap candidate set and a routing service ranks it. ## A typical production shape Positions arrive continuously, so the write path is a stream of `GEOADD`s; the read path is a `GEOSEARCH` with `COUNT` and `ASC`. Because a member has no individual TTL, stale drivers are cleaned by an explicit `ZREM` on logout plus a sweeper that removes members not refreshed recently (usually tracked in a parallel sorted set scored by last-seen timestamp).
- A driver logs off. How do you remove them from the index?With `ZREM key member` — Redis ships no `GEODEL` command. The geo index is a plain sorted set, so all sorted-set write commands apply to it. In practice you also run a sweeper that removes members whose last-seen timestamp (kept in a parallel sorted set) is older than your staleness threshold, because a geo member cannot carry its own TTL.
- How would you return the 20 nearest drivers rather than 20 arbitrary ones inside the radius?Add `ASC` together with `COUNT 20`: Redis then sorts candidates by distance and returns the closest 20. Plain `COUNT 20` without `ASC` gives 20 unordered matches, and `COUNT 20 ANY` returns early with whichever 20 it finds first — faster, but explicitly not the nearest ones.
- Can you search around a point that is not stored in the index?Yes — `GEOSEARCH ... FROMLONLAT <lon> <lat>` takes an arbitrary centre, which is what you use for a pickup location. `FROMMEMBER <member>` is the variant that centres on a member already in the key, e.g. 'other drivers near driver:17'.
It is a phone book sorted by a single number that encodes 'where on the map' — points close on the map usually land close in the book, so a nearby-search is a few range scans plus a distance filter on the candidates.
saying these in an interview costs you the question
- Passing latitude before longitude to GEOADD — it does not error, it just indexes the wrong place
- Expecting a GEODEL command instead of ZREM
- Assuming GEOSEARCH results come back sorted by distance without ASC
- Treating GEODIST as a driving/road distance rather than a great-circle line
- Believing the geo index is its own type with separate persistence or per-member expiry