skies.dev

How to Sort by Geo Distance Using Bounding Boxes in Algolia

4 min read

Geo search in Algolia is good at answering one question: “what is closest to this point?” It gets awkward when the product question is really:

  • show items within a band, not just inside a circle
  • exclude items that are too close
  • keep sorting by distance
  • do all of it without changing the client

That is the problem this article solves.

The Problem

Imagine a marketplace that shows listings near a user. The product team wants a fallback rail:

  • primary rail: items within 30 miles
  • secondary rail: items between 30 and 50 miles
  • both rails sorted by distance from the user

That second rail is not a standard radius query. It is an annulus: everything outside the inner radius and inside the outer radius.

Algolia can rank by distance, but it does not give you a built-in minimum-radius filter that pairs cleanly with geo sorting.

What Does Not Work Well

aroundRadius Only Solves Half The Problem

Algolia’s geo sorting is centered on aroundLatLng and aroundRadius. That is enough if you want “closest items first” or “everything within X miles.”

It does not solve “between X and Y miles.” There is no native inner-radius filter.

insideBoundingBox Breaks Distance Ranking

A common fallback is to build an outer box with insideBoundingBox and then subtract an inner box.

That sounds promising until you hit the catch: insideBoundingBox and aroundLatLng do not play together the way you want for distance-based ranking. If distance order matters, that tradeoff is usually unacceptable.

Client-Side Workarounds Age Poorly

You could ship the logic to every client and patch the UI there. That is fragile when:

  • mobile apps are already deployed
  • multiple clients need the same behavior
  • the search rule is business logic, not UI logic

This is the kind of rule you want to fix once on the server.

Use Numeric Filters As A Geo Boundary

Algolia stores coordinates in _geoloc.

{
  "_geoloc": {
    "lat": 47.6062,
    "lng": -122.3321
  }
}

If your index allows _geoloc.lat and _geoloc.lng as numeric filters, you can build your own bounding boxes.

The approach is:

  • sort by distance with aroundLatLng
  • set aroundRadius to 'all'
  • use numeric filters to keep only the outer box
  • subtract the inner box with a second filter expression

A Practical Pattern

A clean way to think about the query is to split it into two predicates:

const withinOuterBox = [
  `_geoloc.lat < ${upperLeft.lat}`,
  `_geoloc.lat > ${bottomRight.lat}`,
  `_geoloc.lng > ${upperLeft.lng}`,
  `_geoloc.lng < ${bottomRight.lng}`,
].join(' AND ')

const outsideInnerBox = [
  `_geoloc.lat >= ${innerUpperLeft.lat}`,
  `_geoloc.lat <= ${innerBottomRight.lat}`,
  `_geoloc.lng <= ${innerUpperLeft.lng}`,
  `_geoloc.lng >= ${innerBottomRight.lng}`,
].join(' OR ')

const filters = `${withinOuterBox} AND ${outsideInnerBox}`

Then run the search with geo ranking enabled:

index.search('query', {
  aroundLatLng: '47.6062, -122.3321',
  aroundRadius: 'all',
  filters,
})

Why this shape?

  • aroundLatLng keeps the results sorted by proximity
  • aroundRadius: 'all' avoids clipping the search too early
  • the outer box narrows the search space
  • the inner-box exclusion removes the nearby items you do not want

Implementation Checklist

Before you ship this approach, check the following:

  • your index exposes _geoloc for every searchable record
  • _geoloc.lat and _geoloc.lng are configured as numeric filters
  • you have a reliable way to compute bounding boxes from a center point and distances
  • you test edge cases near the international date line and the poles if your app supports global search
  • you confirm your filter syntax with Algolia’s validator before rolling out

Why This Is Worth The Extra Work

This pattern keeps the ranking rule server-side and preserves distance sorting. That matters when the product rule is “show the best nearby alternatives,” not just “show nearby results.”

It is a little more work than a simple radius query, but it is predictable, portable, and easy to reason about once you split the problem into an outer box and an inner exclusion.

Hey, you! 🫵

Did you know I created a YouTube channel? I'll be putting out a lot of new content on web development and software engineering so make sure to subscribe.

(clap if you liked the article)

You might also like