A KD-tree splits points by the median along alternating axes. It answers online nearest-neighbour and rectangle queries in 2D with worst case for rectangles. If the queries can be sorted, an offline sweep with a Fenwick tree is almost always better. The KD-tree is for online geometric queries.

2D KD-tree with rectangle sums and nearest neighbour

struct KD {
    struct P { ll x, y, w; };
    vector<P> p; vector<int> lc, rc; vector<ll> x1, x2, y1, y2, sum;
    int build(int l, int r, int d) {                   // p[l, r), returns node index (= mid)
        if (l >= r) return -1;
        int m = (l + r) / 2;
        nth_element(p.begin() + l, p.begin() + m, p.begin() + r,
                    [d](const P& a, const P& b) { return d ? a.y < b.y : a.x < b.x; });
        lc[m] = build(l, m, d ^ 1); rc[m] = build(m + 1, r, d ^ 1);
        x1[m] = x2[m] = p[m].x; y1[m] = y2[m] = p[m].y; sum[m] = p[m].w;
        for (int c : {lc[m], rc[m]}) if (c >= 0)
            x1[m] = min(x1[m], x1[c]), x2[m] = max(x2[m], x2[c]),
            y1[m] = min(y1[m], y1[c]), y2[m] = max(y2[m], y2[c]), sum[m] += sum[c];
        return m;
    }
    int root;
    KD(vector<P> pts) : p(pts), lc(sz(p)), rc(sz(p)), x1(sz(p)), x2(sz(p)), y1(sz(p)), y2(sz(p)), sum(sz(p)) { root = build(0, sz(p), 0); }
    ll query(int v, ll X1, ll X2, ll Y1, ll Y2) {      // sum of weights in [X1,X2] x [Y1,Y2]
        if (v < 0 || x2[v] < X1 || X2 < x1[v] || y2[v] < Y1 || Y2 < y1[v]) return 0;
        if (X1 <= x1[v] && x2[v] <= X2 && Y1 <= y1[v] && y2[v] <= Y2) return sum[v];
        ll r = (X1 <= p[v].x && p[v].x <= X2 && Y1 <= p[v].y && p[v].y <= Y2) ? p[v].w : 0;
        return r + query(lc[v], X1, X2, Y1, Y2) + query(rc[v], X1, X2, Y1, Y2);
    }
    ll best;                                           // squared distance
    ll boxDist(int v, ll x, ll y) {
        ll dx = max({0LL, x1[v] - x, x - x2[v]}), dy = max({0LL, y1[v] - y, y - y2[v]});
        return dx * dx + dy * dy;
    }
    void nearest(int v, ll x, ll y) {
        if (v < 0 || boxDist(v, x, y) >= best) return;
        ll dx = p[v].x - x, dy = p[v].y - y; best = min(best, dx * dx + dy * dy);
        int a = lc[v], b = rc[v];
        if (b >= 0 && (a < 0 || boxDist(b, x, y) < boxDist(a, x, y))) swap(a, b);
        nearest(a, x, y); nearest(b, x, y);            // closer child first
    }
};

For “nearest other point”, skip the point itself (distance 0) or store ids.

OperationCost
build with nth_element in 2D, in dimensions
rectangle query in 2D; in dims
nearest neighbour on random data, worst case (adversarial inputs exist); degrades badly as grows
insertionrebuild a subtree when it becomes unbalanced (scapegoat, ), or keep static trees of sizes and merge on overflow (the logarithmic method) for amortized insertion
memory

Splitting rule: alternating axes is simplest; splitting on the axis with the larger spread (variance) is more robust for skewed data.

Curse of dimensionality: past –, pruning almost never fires and nearest neighbour degenerates to a scan — for high dimensions use locality-sensitive hashing or approximate methods (HNSW), or accept the linear scan.

Uses: nearest neighbour / nearest neighbours; “points inside a rectangle/circle”; collision detection and ray tracing (though a BVH, below, is usually better for those); -NN classification in ML; closest pair (though divide and conquer is cleaner there).

Alternatives

TaskStructure
rectangle count/sum, queries known in advancesweep + Fenwick, (Offline 2D)
rectangle count, online, static pointspersistent segment tree or wavelet matrix:
rectangle with point updates, online2D Fenwick with compression, BIT of treaps, or KD-tree
which intervals contain sweep + multiset, or interval tree / segment tree storing intervals in canonical nodes
area / perimeter of a union of rectanglessweep + segment tree with cover counts
Manhattan nearest neighbour4 sweeps with rotated coordinates and a BIT (Manhattan Geometry)
Euclidean nearest neighbour for all pointsDelaunay triangulation, or KD-tree
points within distance grid hashing with cell size (check the 9 neighbouring cells)

Named spatial structures beyond the KD-tree, most rare in CP but standard in graphics/GIS/databases — worth recognising by name:

StructureWhat it doesNotes
quadtree / octreerecursively split a square (cube) into 4 (8) equal children, stopping when a cell holds few enough objectsgrid-aligned splits (unlike a KD-tree’s median splits), so structure depends only on the region, not the data; excellent for bounded-integer grids/images; uses: image compression, collision broad-phase, terrain LOD, Barnes–Hut -body
interval tree”which intervals contain ” / “which overlap ”: a balanced BST keyed by interval midpoint, each node storing intervals crossing its centre sorted by both endpointsbuild , query ; in practice a sweep line with a multiset solves the same problems more simply
segment tree (geometric)confusingly, the original computational-geometry segment tree, different from the CP segment tree: stores intervals in canonical nodes, giving stabbing queries in the CP version stores an array for range aggregates; the geometric version stores intervals for point-stabbing — both are trees over a coordinate range, hence the shared name
range treea BST over , each node holding a sorted array of -values in its subtree query ( with fractional cascading), memory; generalises to dims as query / memory; this is the merge sort tree under another name
R-treegroups nearby objects into minimum bounding rectangles, recursively; indexes objects with extent (not just points), boxes may overlapthe standard spatial index in databases (PostGIS, Oracle Spatial, SQLite RTree); insertion heuristics (R-tree, Hilbert R-tree) matter more than the asymptotics, which have no useful worst-case bound
BVH (bounding volume hierarchy)a binary tree of bounding volumes over triangles/objects, built to minimise a surface-area-heuristic cost; query descends, pruning any node the ray missesthe dominant structure in ray tracing — every production renderer uses one

In CP the grid-hash trick and offline sweeps cover most of their uses.