Geohashing and Quadtrees: Build a Production Location Index Without Killing Your Database
Geohashing and quadtrees power Uber, Foursquare, and Pokemon Go.
20+ years shipping large-scale distributed systems. Everything here is grounded in real deployments.
- ✓Deep production experience
- ✓Understanding of internals and trade-offs
- ✓Experience debugging complex systems
Geohashing converts lat/lng to a string that preserves proximity — nearby points share prefixes. Quadtrees partition 2D space into cells for fast range queries. Use geohashes for simple lookups and quadtrees for dynamic spatial joins.
Imagine a map of the world. Geohashing is like assigning a unique zip code to every grid square, where nearby squares have similar zip codes. Quadtrees are like repeatedly folding the map in half — first into four big squares, then each of those into four smaller ones — until each point has its own tiny square. Both let you find 'what's nearby' without checking every single point.
You've got a million delivery drivers, and the app needs to find the nearest one to a new order. If you query the database with a bounding box, you'll hit a full table scan or a slow index scan that kills your p99 latency. I've seen this take down a real-time dispatch system at 2 PM on a Friday — drivers standing still, orders piling up. The fix wasn't more hardware. It was spatial indexing.
Geohashing and quadtrees solve the same core problem: how do you query points by location without scanning every row? They're the reason Uber can match you in under a second and why Pokemon Go knows which monsters are within 100 meters. Without them, every 'find nearby' request is a database massacre.
By the end of this, you'll be able to implement a geohash-based proximity search and a quadtree spatial index from scratch. You'll know when to use each, how to handle edge cases like poles and the international date line, and what breaks in production — because I've debugged all of it.
Why Raw Lat/Lng Indexes Fail at Scale
The naive approach is a composite B-tree index on (lat, lng). It works for exact lookups but fails for range queries. A bounding box query like 'find all points within this rectangle' requires scanning a large portion of the index because lat and lng are independent dimensions. The database can't skip irrelevant rows efficiently. With 10 million points, a bounding box covering 1% of the Earth's surface might scan 100,000 index entries. That's a 100ms query — fine for a dashboard, deadly for an API called 1000 times per second. The real problem: the index doesn't capture proximity. Two points 1 meter apart could be in completely different index pages if their lat/lng values differ in the high-order digits. Spatial indexes solve this by mapping 2D proximity to 1D ordering.
Geohashing: Encoding Proximity into a String
Geohashing solves the proximity problem by interleaving the bits of latitude and longitude into a single value, then base32-encoding it. The result is a string where the longer the common prefix, the closer the points. A geohash of length 5 gives ~5km precision, length 6 ~1km, length 7 ~150m. The key insight: you can index on the geohash string and use a prefix query to find all points within a region. For example, to find points within 5km of a location, compute its 5-character geohash and query WHERE geohash LIKE '9q8yy%'. This is a B-tree range scan on the prefix, which is fast. But there's a gotcha: the geohash grid is a Z-order curve, so points near the edge of a cell might be closer to points in an adjacent cell with a different prefix. You must query the 8 surrounding cells too.