The Hilbert Curve, Explained
After reading this you can construct the Hilbert curve by hand at any order, count the cells it visits, and explain why sorting 2D points along it keeps neighbours close.
What a space-filling curve is
Draw a single unbroken line that starts in one corner of a square and never lifts off the page. Now demand that the line eventually passes through every point of the square, not just a lattice of dots but the whole solid region. That sounds impossible for a one-dimensional line. In 1891 David Hilbert built a sequence of curves whose limit does exactly that.
The explorer draws that sequence one order at a time. At order 1 the curve is a simple U shape with three straight joints, visiting the centres of a 2 by 2 grid. At order 2 the square splits into a 4 by 4 grid and the curve visits all 16 cells. At order 6 it threads through 4096 cells. Each order doubles the grid resolution in both directions, so the line gets four times longer and squeezes into cells four times smaller.
The hook is the colouring. Colour the line by how far along it you have travelled, from blue at the start to red at the end. Then look at any small patch of the square. Its colours vary smoothly, because points that sit near each other in the plane also sit near each other along the curve. That property, not the fractal art, is why the Hilbert curve earns its place in databases and mapping.
How the curve is built: four copies and three joints
The construction is recursive. To draw the order-n curve, take four copies of the order n-1 curve, shrink each to half size, place one in each quadrant, and connect them with three short line segments.
The trick is orientation. If you dropped four identical copies in the four quadrants, the ends would not line up and you could not join them into one continuous path. So the two bottom copies are rotated and reflected. The bottom-left copy is reflected across the main diagonal; the bottom-right copy is reflected across the anti-diagonal. The two top copies keep the parent orientation. With those two flips, the exit of one quadrant lands right next to the entrance of the next, and three joints stitch the four pieces into a single line.
The two diagonal reflections are the whole secret. Without them the curve breaks into four disconnected loops. With them, and only them, the four pieces chain together end to end and the locality property survives every level of recursion.
Because each step replaces one curve with four half-size copies, the number of cells visited multiplies by 4 each order:
Here n is the order and N(n) is the number of grid cells the curve visits. Order 1 gives 4, order 2 gives 16, order 3 gives 64. The grid side length is 2^n, so order n resolves a 2^n \times 2^n grid.
From distance to coordinates: the d2xy map
The curve defines a one-to-one map between a single number d (distance along the curve, an integer from 0 to 4^n - 1) and a grid cell (x, y). Converting d to (x, y) is called d2xy; the reverse is xy2d. Both run in O(n) steps, one step per order.
The idea: read the base-4 digits of d from the coarsest level down. Each pair of bits picks one of the four quadrants, and moving into the two bottom quadrants triggers a rotate-and-flip of the coordinate frame, exactly the reflections from the construction. The core rotation, applied at each level with side length s, is:
Here r_x and r_y are the two quadrant bits extracted at that level, and the flip means replacing x with s-1-x and y with s-1-y. You do not need to memorise this. The point is that the map is cheap and exact, so a database can turn any (x, y) pair into a single sortable integer and back.
Worked example: the order-2 curve, cell by cell
Use the tool defaults: order 2, which fills a 4 by 4 grid. That is 16 cells, so the distance d runs from 0 to 15. Put the origin (0,0) at the bottom-left. Running d2xy for every value of d gives this path:
| d | (x, y) | d | (x, y) |
|---|---|---|---|
| 0 | (0, 0) | 8 | (2, 3) |
| 1 | (0, 1) | 9 | (3, 3) |
| 2 | (1, 1) | 10 | (3, 2) |
| 3 | (1, 0) | 11 | (2, 2) |
| 4 | (2, 0) | 12 | (2, 1) |
| 5 | (3, 0) | 13 | (3, 1) |
| 6 | (3, 1) | 14 | (3, 0)... |
- Check that each step moves exactly one cell horizontally or vertically. From
d=0at (0,0) tod=1at (0,1) is one step up. Fromd=3at (1,0) tod=4at (2,0) is one step right. Every consecutive pair differs by one in exactly one coordinate. That is what "continuous line on the grid" means. - Watch the four quadrants. Cells
d=0..3stay in the bottom-left quadrant, tracing a U that opens upward: this is the reflected copy. Cellsd=4..7sit in the bottom-right, cellsd=8..11in the top-right, andd=12..15in the top-left, ending near the start. Four quadrants, three joints between them. - Test the locality claim. Cells
d=0(0,0) andd=15(0,3) are 15 apart along the curve but only 3 apart vertically in the plane. That is the rare bad case. Now the reverse: cells (1,1) and (1,0) touch in the plane and sit atd=2andd=3, one apart on the curve. Most neighbours behave like the second pair.
Why locality matters for databases and maps
Storage and search work in one dimension. A B-tree index, a sorted file, a range of map tiles: all of them are lines of records, one after another. Geographic data is two-dimensional. To ask "give me everything inside this rectangle" against a one-dimensional index, you need a way to flatten 2D into 1D that keeps nearby points nearby.
Compare two flattening schemes on the same 4 by 4 grid. Row-major order (scan left to right, then jump to the next row) puts cell (3,0) at position 3 and cell (0,1) at position 4, right next to each other. But those cells are far apart in the plane: opposite ends of the grid vertically. Hilbert order never makes a jump that big. The chart below plots, for every pair of cells that are 1 apart in the index, how far apart they are in the plane.
The practical payoff: a query for a small rectangle touches a small number of contiguous ranges of the index, so the disk reads fewer scattered blocks. This is why systems that linearise coordinates, including some spatial indexes and map-tile addressing schemes, prefer Hilbert order over the simpler Z-order (Morton) curve, whose diagonal jumps are longer.
The dimension paradox
The finished curve is one continuous line, which sounds one-dimensional, yet it fills a solid square, which is two-dimensional. Both are true. The resolution is Hausdorff dimension, a way of measuring how a shape's size scales when you shrink your ruler.
Here N is the number of self-similar pieces and r is the scale factor of each piece. For the Hilbert curve, each step makes N = 4 copies at scale r = 1/2. So D = \log 4 / \log 2 = 2. The line has topological dimension 1 but fractal dimension 2. It is a genuine curve that is also, in the limit, a filled square.
The dimension-2 statement holds only for the true limit curve, not for any finite order you can draw. At order 6 the curve is still a plain polyline of length 4^6 = 4096 segments, entirely one-dimensional, with zero area. Every picture the tool shows is an approximation. The dimension-2 result lives at infinity.
Common mistakes
Confusing the drawn curve with the limit. No finite order fills the square. Each order leaves gaps; the gaps just shrink. Claims about area or dimension 2 are limit statements only.
Assuming perfect locality. The curve keeps most neighbours close, but not all. Two cells that touch in the plane can be far apart on the curve when they straddle a joint between quadrants. In the order-2 example, cells (0,0) and (0,3) touch across the grid's left edge only in a stretched sense, but the honest bad case is any point pair split by a high-level fold. Locality is strong on average, not guaranteed for every pair.
Mixing up axis conventions. The d2xy result depends on whether the origin is top-left or bottom-left and which quadrant you call "first." Flip a convention and your coordinates reflect. Pick one and stay consistent, or the same d will land in the wrong cell.
Expecting the curve to be unique. Hilbert's is one space-filling curve among many. The Peano curve, the Z-order curve, and the Gosper flowsnake all fill regions. They differ in locality and in visual shape. Hilbert wins on locality because it never makes a long jump.
Related tools
If the recursion here interested you, the same replace-and-shrink idea drives the Penrose tiling generator and the shape-building in the Chaos game. To measure a fractal's dimension the empirical way, lay grids over a shape in the Box-counting dimension lab (the log-log slope reproduces the D = \log N / \log(1/r) formula above). For other fractals with exact self-similarity, try the Mandelbrot explorer and the Newton fractal. The recursive doubling that gives 4^n cells is the same counting pattern as the 2^n - 1 moves in the Tower of Hanoi. And for another path that visits every cell once, see the Knight's tour.
Frequently asked questions
How many cells does the Hilbert curve visit at order n?
Exactly 4^n. Order 1 visits 4 cells, order 3 visits 64, order 6 visits 4096, order 10 visits 1048576. The grid is 2^n cells on each side.
Why use Hilbert order instead of just scanning row by row?
Row-major scanning makes a long jump at the end of every row: cell (3,0) and cell (0,1) are index-adjacent but 3 cells apart in the plane on a 4 by 4 grid. The Hilbert curve never jumps more than one cell between consecutive index positions, so a rectangular query maps to fewer scattered ranges in a sorted index.
Is the Hilbert curve the same as Morton or Z-order?
No. Z-order (Morton) interleaves the bits of x and y, which is faster to compute but makes long diagonal jumps between quadrants. The Hilbert curve costs a rotate step per level but keeps every step to one cell, giving better locality. Both flatten 2D to 1D.
Can a curve really have dimension 2?
The limit curve has Hausdorff dimension 2 because it makes 4 copies at half scale, and \log 4 / \log 2 = 2. It is still topologically a curve (dimension 1). Any drawing at finite order is a plain line with zero area; dimension 2 is a property of the infinite limit only.
Does the curve pass through a point more than once?
The limit curve is continuous and onto (it hits every point), but it is not one-to-one: some points are visited more than once. That is unavoidable for a continuous map from a line onto a square. The finite-order approximations the tool draws never revisit a cell.