Mathematics
📅 July 9, 2026 ⏱ ~9 min read

Hilbert Curve and Space-Filling Curves — Locality, Indexing & Compression

A curve that visits every point of a square without ever crossing itself, and why databases, GPUs and image codecs all quietly rely on it to turn scattered 2D and 3D data into cache-friendly 1D sequences.

1. Peano's Paradox: A Line That Fills a Square

In 1890, Italian mathematician Giuseppe Peano constructed something that seemed to violate common sense: a continuous curve — a single, unbroken 1-dimensional path — that passes through every single point of a 2-dimensional square. Before Peano, mathematicians assumed a curve, being fundamentally 1-dimensional, simply could not have "enough points" to cover an entire 2D area. Georg Cantor had already shown that the set of points in a line and the set of points in a square have the exact same cardinality (both are uncountably infinite, in bijection with each other) — but Peano's construction went further, showing that this correspondence could be made continuous: you could trace out the mapping with a pen, never lifting it, and eventually paint every point of the square.

This class of curves is called space-filling curves. They are continuous (no jumps) but famously nowhere differentiable — they have no well-defined tangent direction at any point, because they double back and change direction infinitely often at every scale, similar in spirit to a fractal curve of dimension exactly 2.

The dimension twist: Space-filling curves have topological dimension 1 (they are curves, parameterized by a single real number t) but Hausdorff dimension exactly 2 (they fill an area). This was one of the first clear demonstrations that "dimension" is not a single simple concept — different rigorous definitions of dimension can disagree even for a well-defined, explicitly constructible object.

David Hilbert refined Peano's construction in 1891 into a cleaner, more visually intuitive recursive rule, and it is Hilbert's version — not Peano's original zig-zag — that is used almost universally in computing today, because of one crucial extra property Hilbert's construction has: locality.

2. Hilbert's Recursive Construction

The Hilbert curve is built recursively. Start with a simple U-shaped path visiting the four quadrants of a square in order. To get the next level of detail, replace each of the four quadrants with a smaller, appropriately rotated and/or reflected copy of the same U-shape, chosen so that the exit point of one sub-curve lines up exactly with the entry point of the next. Repeat this substitution indefinitely, and in the limit the curve visits every point of the square.

Hilbert curve construction (order n): Order 1: 4 quadrants, visited in a U-shaped path Order n: each quadrant of order (n−1) curve, rotated/reflected so endpoints connect seamlessly At order n, the curve visits 4ⁿ grid cells (a 2ⁿ × 2ⁿ grid, fully traversed with unit steps) Key guarantee: consecutive integers i, i+1 on the curve → ALWAYS map to spatially adjacent grid cells

The rotations at each recursive level are exactly what makes the Hilbert curve special: they guarantee that wherever one sub-square's path ends, the next sub-square's path picks up right next to it in physical space, not just in the abstract 1D ordering. Applied recursively, this gives a global guarantee: consecutive indices along the entire curve are always physically adjacent cells, at every scale, everywhere on the grid — a property no simpler recursive scheme achieves as well.

// Convert a 1D Hilbert distance d to (x, y) grid coordinates, order n
// (Classic bit-twiddling algorithm; n = number of bits per axis)
function hilbertD2XY(n, d) {
  let [x, y] = [0, 0];
  let t = d;
  for (let s = 1; s < n; s *= 2) {
    const rx = 1 & (t / 2 | 0);
    const ry = 1 & (t ^ rx);
    // Rotate the quadrant so its entry/exit aligns with the parent curve
    if (ry === 0) {
      if (rx === 1) { x = s - 1 - x; y = s - 1 - y; }
      [x, y] = [y, x];  // swap (reflect across diagonal)
    }
    x += s * rx;
    y += s * ry;
    t = Math.floor(t / 4);
  }
  return [x, y];
}
3D and higher dimensions: The same recursive substitution generalizes to a 3D Hilbert curve (visiting the 8 octants of a cube) and to arbitrary dimension d, at the cost of more complex rotation rules. 3D Hilbert curves are used for indexing volumetric medical scan data and 3D spatial databases.

3. Hilbert vs. Morton (Z-Order) Curves

A simpler, faster-to-compute alternative is the Morton order, also called the Z-order curve: interleave the bits of the x and y coordinates directly to produce a single index. It shares the recursive quadrant idea with the Hilbert curve, but skips the rotation step — which makes it dramatically simpler and faster to compute, at a real cost in locality quality.

// Morton (Z-order) encode: interleave bits of x and y
function mortonEncode(x, y) {
  function spread(v) {
    v &= 0x0000ffff;
    v = (v | (v << 8)) & 0x00ff00ff;
    v = (v | (v << 4)) & 0x0f0f0f0f;
    v = (v | (v << 2)) & 0x33333333;
    v = (v | (v << 1)) & 0x55555555;
    return v;
  }
  return spread(x) | (spread(y) << 1);
}

Hilbert Curve

O(n) recursive rotation

Best-possible locality: consecutive indices are always spatially adjacent. Slightly slower encode/decode.

Morton (Z-order)

Bit interleaving

Extremely fast (a handful of bitwise ops). Locality is good on average but has occasional large "jumps" at quadrant boundaries.

The Z-order curve's weakness is visible in its name: tracing consecutive Morton indices draws a Z-shape, and every time the curve crosses from one quadrant to the next, it can leap clear across the grid — because a Morton code's high bits can flip while low bits reset, producing a large jump in physical space for a change of just 1 in the index. The Hilbert curve's rotations exist specifically to eliminate these jumps, making its locality provably optimal, at roughly 2–4× the computational cost of Morton encoding/decoding.

4. Why It Matters: Databases, Caches, Compression

Spatial database indexing. PostGIS, geographic information systems, and time-series-with-location databases map 2D or 3D coordinates to a single Hilbert index and store rows sorted by that index in a conventional B-tree. Because the Hilbert curve preserves locality so well, a contiguous range of Hilbert indices corresponds closely to a compact geographic region, turning expensive multidimensional range queries into fast 1D B-tree range scans.

CPU and GPU cache locality. Traversing a 2D texture, height field, or grid in raster (row-major) order causes cache-unfriendly access patterns whenever you move to a new row. Storing the same data in Hilbert-curve order instead means that any small local neighborhood of pixels or grid cells is also a small contiguous run of memory addresses — dramatically improving cache hit rates for algorithms like mipmap generation, tiled texture streaming, and image blur/convolution kernels.

Distributed systems and load balancing. Systems that shard data across many machines by geographic or spatial key (delivery routing, geospatial sharding, distributed hash tables with locality requirements) use Hilbert indices so that nearby real-world locations land on the same or nearby shards, minimizing costly cross-shard queries.

Image and video compression. Some image compression and dithering algorithms traverse pixels in Hilbert-curve order rather than raster order specifically because it keeps visually similar (spatially nearby) pixels close together in the resulting 1D data stream, which helps entropy coders and color-quantization algorithms exploit local correlation more effectively than a raster scan would.

Procedural generation and streaming worlds. Open-world games that stream terrain chunks in and out of memory as a player moves often use a Hilbert or Morton ordering for chunk indices, so that spatially nearby chunks are also nearby in the streaming priority queue and on-disk layout — reducing seek costs and improving prefetch accuracy.

Explore Math Simulations

Visualize curves, fractals and geometric structures interactively in your browser.

Explore Math Simulations →

Related Articles

Frequently Asked Questions

What is a space-filling curve?

A space-filling curve is a continuous curve whose image, as its 1-dimensional parameter runs from 0 to 1, passes through every point of a 2D (or higher-dimensional) region — for example, the entire unit square. Giuseppe Peano constructed the first example in 1890, startling mathematicians because it showed a 1-dimensional curve could have the same cardinality of points as a 2-dimensional area, contradicting naive intuitions about dimension. Space-filling curves are continuous but nowhere differentiable, and modern examples like the Hilbert curve are widely used in computing for locality-preserving 1D-to-2D mapping.

Why is the Hilbert curve better than the Z-order (Morton) curve for locality?

Both curves map a 1D index to 2D/3D coordinates while trying to keep nearby indices spatially close, but the Z-order curve's recursive quadrant jumps produce occasional long "jumps" across the grid at boundaries between quadrants, because consecutive Morton codes can differ in a high bit, teleporting across the whole grid. The Hilbert curve, built with rotations that align the entry and exit points of each sub-quadrant, guarantees that consecutive points on the curve are always adjacent in space — no long jumps ever occur. This makes the Hilbert curve's locality provably optimal among space-filling curves, at the cost of a slightly more complex construction and decode algorithm than Morton order's simple bit-interleaving.

How is the Hilbert curve used in databases and spatial indexing?

Spatial databases (PostGIS, geographic information systems, R-tree alternatives) map 2D or 3D coordinates to a single Hilbert curve index (a Hilbert distance) and then use that single number as a conventional B-tree index key. Because the Hilbert curve preserves locality so well, ranges of nearby Hilbert indices correspond closely to nearby geographic regions, so a single-dimensional range query on the index approximates a 2D spatial region query — turning expensive multidimensional range searches into fast, well-understood 1D B-tree lookups.

Who invented the Hilbert curve and when?

David Hilbert published his construction in 1891, one year after Giuseppe Peano's original 1890 space-filling curve. Hilbert's version is described as a simpler, more visual recursive rule based on repeatedly subdividing a square into four quadrants and connecting them with rotated copies of the same U-shaped path — and it is Hilbert's construction, not Peano's original, that dominates modern computing applications because of its superior locality-preservation property.

What does "nowhere differentiable" mean for a space-filling curve?

A space-filling curve changes direction infinitely often at every scale of magnification, so it has no well-defined tangent line (derivative) at any point along its length — similar to how a Koch curve or Weierstrass function is continuous everywhere but smooth nowhere. This is a necessary consequence of a 1-dimensional curve covering a full 2-dimensional area: a smooth (differentiable) curve simply cannot have enough "room" to fill space this densely.

How do you compute Hilbert curve coordinates in code?

The standard algorithm converts a 1D Hilbert distance d into (x, y) grid coordinates (or the reverse) using an iterative bit-processing loop: at each recursive scale, extract two bits from d to determine the current quadrant, then conditionally reflect and swap the accumulated x/y coordinates to apply the correct rotation for that quadrant before adding the quadrant's offset. This runs in O(log n) time for an n×n grid, where n is a power of 2.

What is Morton order / Z-order curve?

The Morton order (Z-order curve) encodes 2D or 3D coordinates into a single index by interleaving the binary digits of each coordinate — for 2D, alternating bits from x and y. It is dramatically faster to compute than a Hilbert curve (just bit-shifting and masking operations) and is used wherever raw encode/decode speed matters more than perfect locality, such as GPU texture tiling schemes and some spatial hash implementations.

Are space-filling curves used in image compression?

Yes. Some image compression, dithering and color-quantization algorithms traverse pixels in Hilbert-curve order instead of standard raster (row-major) order, because the Hilbert ordering keeps spatially adjacent (and therefore likely visually similar) pixels adjacent in the resulting 1D data stream. This improves the effectiveness of entropy coders and reduces visible patterning artifacts compared to a raster-order traversal.

Does the Hilbert curve generalize to 3D and higher dimensions?

Yes — the same recursive substitution generalizes to a 3D Hilbert curve that visits the 8 octants of a cube, and to arbitrary dimension d with correspondingly more complex rotation rules for each recursive step. 3D and higher-dimensional Hilbert curves are used for indexing volumetric data such as medical CT/MRI scans, 3D spatial databases, and high-dimensional nearest-neighbor search structures.