Naive scan
Grid index
Speedup
Drag on the map to move the query. Benchmark runs 200 queries of each method.
The naive version: O(n) per query
The obvious implementation loops over every user and computes a distance: users.filter(u => dist(u, me) < r). With 500 users it's instant. With 100,000 users and thousands of queries per minute, every single query pays for every single user — even the ones on the other side of the map. That is exactly why proximity features feel fast in a demo and fall over in production.
The fix: a grid spatial index
Chop the world into cells of size roughly equal to your search radius, and bucket each user into their cell once. A query then only inspects the handful of cells the search circle overlaps — highlighted in yellow above — and runs the exact distance check on just those candidates. Same correct results, a tiny fraction of the work.
Watch the counters: at 100k clustered users the naive scan checks 100,000 points per query while the grid checks a few hundred. That gap is where 50x speedups come from. Production systems use the same idea with fancier structures — geohashes, quadtrees, R-trees, or PostGIS's GiST indexes — but the principle is identical: prune first, compute distance second.
Tuning matters
Try shrinking the cell size far below the radius: the index scans many cells and overhead grows. Make cells huge and each cell holds too many candidates. A cell size near your typical search radius is usually the sweet spot. Verified, benchmarked rewrites like this — same output, way less work — are how a slow hot path becomes a fast one.