Skip to main content
Geospatial Indexinglesson 3 of 4 · 2 min read

The Boundary Problem

The shop across the street

Geohash has a bug every team meets exactly once, and it arrives as a report saying the shop opposite my flat is missing from nearby.

The prefix property only runs one way. Points sharing a long prefix are close, and that is what makes your index work. The reverse is simply false: two points can be metres apart and share nothing at all.

A box boundary is a hard line. Two points on either side of it land in different halves at that bit, so their strings diverge at that character and everything after it. Walk 20 seconds across the boundary and your strings agree on 4 characters instead of 6.

The worst case is a corner. Your user is standing where four boxes meet, so their neighbours are in all four. A query on their own box returns whichever fraction of nearby places happens to share their quadrant.

This never looks like a crash. It looks like slightly wrong results, and that is precisely why it survives code review and ships.

Nine boxes, not one

Stop querying one box and the bug goes away. Work out your user's box and the 8 around it, then run 9 prefix scans. Put the candidates together and apply the exact distance filter you were always going to run anyway.

With a box bigger than your search radius, that 3 by 3 block must contain every point inside the radius. That is the entire reason for the sizing rule in the last lesson.

The cost is small, and it is not optional. Nine index scans instead of one, and a candidate set roughly nine times larger before you filter it. Every production geospatial query pays exactly this. If you find a nearby feature querying a single box, you have found a bug, not a clever optimisation.

the shape of it
tdnzqneighbourtdnzwneighbourtdnzxneighbourtdnzpcafe is heretdnzruser is heretdnztneighbourtdnznneighbourtdnzmneighbourtdnzjneighbour40 m, no prefix
Querying only the user's own cell drops everything across each boundary, so the search must cover all 9.
why one cell is a bug and nine cells is correct
Java
// Wrong: the shop across the street can sit in the next cell and
// share no prefix at all, so it never appears.
List<Shop> wrong(String cell) {
  return db.query("SELECT * FROM shops WHERE geohash LIKE ?", cell + "%");
}

// Right: the user's cell plus the eight around it, then filter by
// real distance because cells are rectangles and the search is a circle.
List<Shop> nearby(double lat, double lon, double radiusM) {
  String cell = geohash(lat, lon, 6);            // ~610 m, bigger than radius
  List<String> nine = withNeighbours(cell);      // 3 x 3 block

  return db.query("SELECT * FROM shops WHERE geohash_6 IN (?)", nine)
           .stream()
           .filter(s -> haversine(lat, lon, s.lat, s.lon) <= radiusM)
           .toList();
}

Worked example

Priya gets a support ticket from a user in Bengaluru: a cafe she can see from her window does not appear in nearby results, while one 900 m away does. Priya computes both geohashes and the cause is immediate: the user sits at tdnzr, the cafe across the road at tdnzp, sharing 4 characters where the query demanded 6. The single-cell query never had a chance. She changes the lookup to compute the 8 neighbours and query all 9 prefixes, which takes the candidate count from about 800 to about 7,000 and adds 6 ms before the distance filter runs. The missing cafe appears, and a regression test now asserts that two points 40 m apart on opposite sides of a known cell boundary both return.