One line of arithmetic, repeated
The Mandelbrot set is defined by an iteration so short it fits in a tweet. Take a complex number c. Start at z = 0 and apply, over and over:
z_{n+1} = z_n² + c with z_0 = 0
c is IN the set ⟺ the sequence stays bounded forever
c is OUT of the set ⟺ the sequence runs away to infinity
That is the whole definition. Every spiral, every filament, every miniature copy of the set buried a trillion zooms deep is a consequence of squaring and adding. The set is named for Benoît Mandelbrot, who produced the first pictures of it at IBM in 1980 and recognised what he was looking at; the underlying complex dynamics had been studied by Pierre Fatou and Gaston Julia around 1918, entirely without computers.
The escape-time algorithm
You cannot iterate forever, so you need a test that terminates. It is provided by a small theorem: if |z| ever exceeds 2, the sequence is guaranteed to diverge and never comes back. The reason is simple — once |z| > 2 and |z| ≥ |c|, squaring grows the modulus faster than adding c can restrain it. So an escape radius of 2 is exact, not a fudge.
function escape(cx, cy, maxIter) {
let x = 0, y = 0, x2 = 0, y2 = 0, i = 0;
while (x2 + y2 <= 4 && i < maxIter) { // compare squares: no sqrt
y = 2 * x * y + cy; // 3 multiplications per
x = x2 - y2 + cx; // iteration, not 4
x2 = x * x;
y2 = y * y;
i++;
}
return i; // i === maxIter → assume inside
}
Points that never escape simply exhaust maxIter, and you paint them black. This is the fundamental asymmetry of rendering the set: escape is provable, membership is not. Every black pixel is a guess whose confidence is bounded by your iteration limit — and as you zoom in, the boundary structures need ever more iterations to resolve, which is why deep zooms get slower even at a constant resolution.
Making it smooth, and making it fast
Colouring by the raw integer i produces hard concentric bands, because i is a step function. The fix is the normalised iteration count, which interpolates between bands using how far past the escape radius the final z overshot:
// after the loop escaped with |z|² = x2 + y2 const logZn = Math.log(x2 + y2) / 2; const nu = Math.log(logZn / Math.log(2)) / Math.log(2); const smooth = i + 1 - nu; // a real number, not an integer
Feed that real-valued count into a palette and the bands dissolve into a continuous gradient. Two other standard accelerations matter far more than micro-optimising the inner loop:
interior checks The main cardioid and the period-2 bulb have exact
algebraic tests. Points inside them are in the set,
so skip the iteration entirely. This alone removes
most of the maxIter-bound work in a full-set view.
period detection A bounded orbit eventually cycles. Keep a reference
(Brent / cycle z every few iterations and bail out early when the
detection) orbit returns to within epsilon of it — the point is
in the set and there is no need to reach maxIter.
The cardioid test is worth writing out, because it is cheap and it saves a great deal:
const q = (cx - 0.25) ** 2 + cy * cy; if (q * (q + (cx - 0.25)) <= 0.25 * cy * cy) return maxIter; // main cardioid if ((cx + 1) ** 2 + cy * cy <= 0.0625) return maxIter; // period-2 bulb
What the boundary actually is
The interior is dull; all the structure is on the boundary. Three facts worth carrying around. First, the set is connected — Adrien Douady and John Hubbard proved it in 1982. The islands you see floating in the black sea at high zoom are not islands: they are joined to the main body by filaments too thin to render.
Second, the boundary has Hausdorff dimension exactly 2 — Mitsuhiro Shishikura, 1998 — even though it has zero area. It is as crinkled as a curve in the plane can possibly be. Third, the set is self-similar only approximately: the little copies scattered along the filaments are not exact rescalings of the whole, and each is decorated differently. The Mandelbrot set is a fractal in the sense of endless detail at every scale, not in the sense of exact self-similarity like the Sierpiński triangle.
There is also a direct relationship with the Julia sets, which use the same iteration but hold c fixed and vary z₀. The Mandelbrot set is precisely the map of which c gives a connected Julia set: pick a c inside it and the corresponding Julia set is one connected piece; pick one outside and the Julia set shatters into Cantor dust. Every point of the Mandelbrot set is a thumbnail index into a different Julia set — which is why an explorer that shows both side by side is so much more illuminating than either alone.
The floating-point wall
Zoom in far enough and the picture turns to mush — not because the set runs out of detail, but because double precision runs out of digits. A 64-bit double carries about 15–16 significant decimal digits, so once the width of your viewport falls to roughly 10⁻¹⁵ of the original, adjacent pixels round to the same number and the image goes blocky.
Serious deep-zoom renderers escape this with perturbation theory: compute one high-precision reference orbit at the centre of the view in arbitrary-precision arithmetic, then express every other pixel as a small delta from that reference and iterate the delta in ordinary double precision. The deltas stay small, so the precision loss does not bite, and only one orbit needs the slow big-number arithmetic. Add a series approximation to skip the first thousands of iterations of each delta orbit, and zooms far beyond 10⁻³⁰⁰ become practical — which is how the deep-zoom videos you have seen are made. The one hazard is glitches: pixels whose orbit passes too close to zero relative to the reference lose accuracy and must be detected and re-rendered against a second reference.
Frequently asked questions
Why is the escape radius exactly 2?
Because once |z| exceeds 2 (and |z| ≥ |c|), squaring increases the modulus faster than adding c can pull it back, so the sequence is guaranteed to run away to infinity. It is a proved bound, not an empirical threshold — no orbit that leaves the disc of radius 2 ever returns.
Why does my deep zoom turn into blocks?
Double-precision floating point carries only about 15-16 significant digits. Once the viewport is around 10⁻¹⁵ of the original width, neighbouring pixels round to the same coordinate. Deep-zoom renderers avoid this with perturbation theory: one arbitrary-precision reference orbit plus small per-pixel deltas iterated in double precision.
Is the Mandelbrot set self-similar?
Not exactly. It has infinite detail at every scale, and it contains countless small copies of itself, but those copies are distorted and each is decorated differently — so it is not exactly self-similar the way a Sierpiński triangle or a Koch curve is. Its boundary has Hausdorff dimension 2 while enclosing zero area.
Try it live
Everything above runs in your browser — open Mandelbrot Set Explorer and change the parameters while it is running. Nothing is installed, nothing is uploaded, the whole model lives in one tab.
▶ Open Mandelbrot Set Explorer simulation