rishikesh.s / notes

Bézier curves from first principles

Every glyph in this sentence, every icon in your toolbar and the way this page's animations speed up and slow down all come from one small idea, applied twice. I finally sat down to understand it properly. These are my notes, built up from a straight line, with demos you can drag.

A bit of history first, because I enjoyed it. In 1962 Pierre Bézier was an engineer at Renault with a problem: car bodies were designed as physical clay models, and there was no good way to describe their surfaces to the milling machines that had to cut them. The curves he popularised were actually worked out a few years earlier by Paul de Casteljau at Citroën, who wasn't allowed to publish. The idea in both places was the same. Pin a curve down with a handful of points, and it can be reproduced exactly, anywhere, at any scale.

Sixty years later the same family of curves is the backbone of computer graphics. Fonts, SVG icons, Illustrator's pen tool, CSS animation easing, robot motion planning. I knew the name for years without knowing how they actually work, and it turns out the construction is genuinely simple. The only prerequisite is knowing how to walk along a straight line.

Start with a line

Take two points, A and B, and a number t between 0 and 1. Linear interpolation (lerp, to its friends) gives you the point that is a fraction t of the way from A to B:

lerp(A, B, t) = (1 − t) · A  +  t · B

At t = 0 you're standing on A, at t = 1 you're on B, at t = 0.5 you're exactly halfway. Sweep t from 0 to 1 and you trace the whole segment. That's the entire toolbox. A straight line traced this way is technically already a Bézier curve, a degree-1 one with control points A and B. Everything that follows is just lerp applied to the results of other lerps, which honestly surprised me when it clicked.

De Casteljau's algorithm

Now take three points: P₀, P₁ and P₂. For a given t, do this:

  • Lerp from P₀ to P₁, call the result Q₀.
  • Lerp from P₁ to P₂ with the same t, call it Q₁.
  • Lerp from Q₀ to Q₁. That point is on the curve.

Sweep t from 0 to 1 and that final point sweeps out a smooth arc: a quadratic Bézier curve. The recursive walk is called De Casteljau's algorithm. Try it below. Drag the points, scrub t, and watch the two-stage construction happen:

t = 0.400
Fig. 1 · the two grey lerps produce the amber points; the amber lerp produces the point on the curve. Drag any control point; handles also move with arrow keys when focused.

The thing that made this click for me is what the amber line is doing: at every t it is tangent to the curve. The construction doesn't just give you points, it gives you directions for free. That's why the same machinery can drive font rendering and also steer a CNC cutter along a path.

One more level: the cubic

Nothing stops you at three points. With four control points you lerp three times to get three points, lerp those to get two, and lerp once more to land on the curve. That's a cubic Bézier, and it's the workhorse of the whole field. It's the cheapest curve that can hold an inflection (an S-bend), and you get independent control over the direction the curve leaves each endpoint.

t = 0.350
Fig. 2 · a cubic is three nested layers of lerp. Try dragging P₁ across the line from P₀ to P₃ to put an S-bend in it.

Unrolling the recursion gives the closed form. For the cubic, collect the terms and each control point ends up weighted by a polynomial in t:

B(t) = (1−t)³ P₀  +  3(1−t)²t P₁  +  3(1−t)t² P₂  +  t³ P₃

Those weights, (1−t)³, 3(1−t)²t, 3(1−t)t², t³, are the Bernstein basis polynomials, and two things about them are worth staring at. They are the terms of the binomial expansion of ((1−t) + t)³, so at every t they sum to exactly 1: the curve point is always a weighted average of the control points. And each weight peaks at a different t, which is the precise sense in which P₁ "pulls" the early part of the curve and P₂ the late part. The curve passes through its endpoints (B(0) = P₀, B(1) = P₃) but only approaches the middle control points. It never touches them.

The derivative falls straight out of the algebra, and it's another Bézier curve, one degree lower:

B′(t) = 3(1−t)² (P₁−P₀)  +  6(1−t)t (P₂−P₁)  +  3t² (P₃−P₂)

At t = 0 that collapses to 3(P₁−P₀): the curve leaves P₀ aimed directly at P₁. So a pen tool's "handles" are a literal picture of the derivative at each anchor. I'd been dragging them for years without knowing that.

Why these curves won

Plenty of curve families existed before Bézier's. As far as I can tell, this one took over because a short list of properties happens to be exactly what designers, font engineers and renderers need:

  • The control points mean something. Endpoints are on the curve; handles set the tangents. You can hand the four points to a person with a mouse and they can steer the curve by feel. Try explaining a polynomial's coefficients to anyone.
  • Convex hull. Because the Bernstein weights are non-negative and sum to 1, the curve can never escape the convex hull of its control points. A renderer can trivially bound a curve, cull it, or test it for intersection without evaluating anything.
  • Affine invariance. Rotate, scale or skew the control points and the curve transforms with them, exactly. You transform 4 points instead of 200 sampled ones, which is why a font scales from 8px to a billboard with no loss.
  • Variation diminishing. The curve wiggles no more than its control polygon does: a line crosses the curve at most as many times as it crosses the polygon. No surprise oscillations, which is what makes the curves feel predictable under the cursor.
  • Cheap to evaluate, cheap to split. A handful of lerps per point, in fixed or floating point, no trigonometry anywhere.

Where you've already used them today

Fonts. A glyph outline is a loop of Bézier segments. TrueType (.ttf) uses quadratics, picked in the late '80s because they're cheaper to rasterise, while PostScript and CFF/OpenType outlines use cubics. When this page loaded, your browser took the curve descriptions out of three font files and flattened several thousand of them into the pixels you're reading right now.

Vector graphics. SVG's path syntax exposes the machinery directly: Q is a quadratic segment, C a cubic. These two paths are figures 1 and 2 of this page, control point for control point:

<!-- one quadratic, one cubic: figures 1 and 2 -->
<path d="M 70 300  Q 320 60   570 300" />
<path d="M 70 300  C 180 70   460 70   570 300" />

Every pen tool, whether it's in Illustrator, Figma, Inkscape or Glyphs, is an editor for exactly these commands. When you drag a handle and the curve follows, you are moving P₁ in figure 2.

Animation easing. CSS's cubic-bezier(x1, y1, x2, y2) is a cubic in disguise, with a twist: the axes aren't space, they're time (x) against progress (y). P₀ is pinned at (0,0) and P₃ at (1,1); you only get to place the middle two points. The browser then answers "at 40% of the duration, how far along is the animation?" by intersecting the curve with a vertical line. That's why the spec clamps x₁ and x₂ to [0,1]: time isn't allowed to flow backwards. The y values are unclamped, and pushing them outside [0,1] is how you get overshoot and bounce.

cubic-bezier(.22, .70, .20, 1)
Fig. 3 · drag the two handles, then run to feel the easing. The default is this site's own transition curve, --ease in its stylesheet. Drag a handle's y outside the plot to make the dot overshoot.

Everything that moves along a path. Camera rails in games, GPS route smoothing, the toolpath of a 3D printer, PowerPoint's motion paths, robot arm trajectories. Any time something needs to get from A to B without a visible kink, there's a good chance a cubic Bézier is doing the steering.

How a curve becomes pixels

A GPU rasteriser wants straight lines, so at some point every Bézier on your screen gets flattened into a polyline. The naive way is to sample a fixed number of t values, but that wastes segments on the straight-ish parts and starves the tight bends. The standard answer uses a property of De Casteljau's construction that I did not see coming: the intermediate points from figure 2 don't just locate one point, they split the curve into two smaller Béziers whose control points hug the curve more tightly than the original four.

So the renderer recurses: if a curve's control points are within tolerance of a straight line, emit a segment; otherwise split at t = ½ and repeat. Flat regions terminate early, bends subdivide deeper, and the error is bounded everywhere:

12 segments
Fig. 4 · adaptive subdivision. Tighten the tolerance and segments appear exactly where the curvature is; drag the points and watch the subdivision redistribute itself.

What they can't do

Two honest limitations, and what the field does about them.

No polynomial curve is a circle. A cubic can only approximate an arc. The classic trick pins a cubic to a quarter circle and asks how long the handles should be; the answer is k ≈ 0.5523 · r, a number quietly hard-coded into nearly every graphics library that offers rounded corners. The error over a quarter circle is about 0.02% of the radius. Invisible at any screen size, which is why nobody minds.

Fig. 5 · one cubic (solid) laid over a true circle (faint). Each handle is tangent to the circle with length k·r, k ≈ 0.5523. Four of these make the "circles" in most icon sets.

One curve doesn't scale to complex shapes. You could raise the degree to ten or eleven control points, but high-degree Béziers are miserable: moving any single point bends the entire curve, and the shape drifts ever further from its control polygon. So practice goes the other way: keep the pieces cubic and chain them. As long as each joint's incoming and outgoing handles are collinear, the seam is invisible. That is exactly what a pen tool's "smooth point" enforces, and why breaking the handles gives you a corner. A chain of Béziers is a spline. Add per-point weights so arcs come out exact and you get NURBS, the standard in CAD, and a direct descendant of what Bézier and de Casteljau built for car bodies.

Further reading

Things I leaned on while figuring this out:

← All writing