← All articles

How we built it: resolving tax jurisdictions in under a millisecond

Priya Venkat · 2 min read

Our customers expect tax to be calculated correctly and instantly, wherever the transaction happens. In some countries that means choosing between thousands of overlapping local rates, and the boundaries that decide which rate applies change every quarter.

This post walks through the system we built to answer that question in well under a millisecond.

Postal codes are not enough

The obvious approach is to key rates by postal code. It does not work: postal codes describe delivery routes, not tax districts.

Two neighbouring houses in different tax districts
Two neighbouring addresses, two different combined rates

Given that postal codes do not work, we needed to place every address inside the precise boundary polygons published by each authority. That raised two problems:

  1. The public data is uneven. Neighbouring authorities publish boundaries that overlap or leave gaps of a few metres — enough to put a house in the wrong district.

  2. The polygons are enormous. Some have tens of thousands of vertices, and a naive point-in-polygon test is linear in vertex count.

Offline: building places of taxation

Offline, we split each country into non-overlapping regions where every point pays exactly the same set of taxes. We call these regions places of taxation.

build_regions.py
def build_regions(authorities):
    regions = [world_polygon()]
    for authority in sorted(authorities, key=lambda a: a.level):
        regions = [piece
                   for region in regions
                   for piece in split(region, authority.boundary)]
    return [r for r in regions if r.area > MIN_AREA]

The fastest lookup is the one you did last night.

Online: matching an address in a millisecond

At checkout we geocode the address once, then search a bounding-box index before touching any polygon.

Bar chart of lookup latency improvements
Share of lookups answered from the bounding-box index alone

Stage

p50

p99

Geocode

0.21 ms

0.80 ms

Box index

0.02 ms

0.06 ms

Polygon test

0.09 ms

0.41 ms

What we learned

  • Precompute everything that does not depend on the request.

  • Treat public boundary data as a draft, not a source of truth.

  • Measure the slow tail, not the median.

0 comments

  • Be the first to comment.