How the Gram-Schmidt Process Works

What do you do when your coordinate axes aren't at right angles? Here's how the Gram-Schmidt process turns any set of independent vectors into a clean, perpendicular basis, one shadow at a time.

By Petrus Sheya

July 25, 2026 · 5 min read

What happens when your coordinate axes aren't at right angles?

Say you've got two directions in the plane, v1v_1 and v2v_2. They're not parallel, so together they can still reach every point around them. But they lean into each other. Distances get warped. Angles lie to you. Every formula that quietly assumes "xx" and "yy" are perpendicular breaks the moment they aren't.

We want to fix that, without changing what the vectors can reach.

We want a new pair of vectors that span the same space but stand at a perfect right angle to each other. That's what the Gram-Schmidt process does. And it does the whole thing with one repeated trick: subtract the shadow.


What does it mean for a vector to "lean into" another?

Picture a flagpole standing straight up, and the sun directly overhead of some other vector, bb. The sun casts a shadow of bb straight down onto the flagpole's line.

That shadow is a real, measurable thing. It's the part of bb that already points in the flagpole's direction. We call it the projection of bb onto aa.

Drag bb around below and watch its shadow move along aa.

Drag the tip of b. The shadow it casts onto a is its projection.

abshadow
a · b14.00
Shadow length2.75
Angle(a, b)52°

Notice what happens at 90°. When bb is perpendicular to aa, the shadow shrinks to nothing. That makes sense, a vector that points straight up over a flagpole casts no shadow along it at all. Zero shadow is exactly what "perpendicular" means.

Now for the notation. We write the shadow, the projection of bb onto aa, as:

proja(b)=abaaa\text{proj}_a(b) = \frac{a \cdot b}{a \cdot a}\, a

The dot product aba \cdot b measures how much bb agrees with aa's direction. Dividing by aaa \cdot a accounts for aa's own length. Multiplying back by aa points the result back along the flagpole. Three moves, one shadow.


How do we make two vectors perpendicular?

Once you can compute a shadow, orthogonalizing two vectors is almost anticlimactic: subtract it.

Keep the first vector exactly as it is, call it u1=v1u_1 = v_1. It becomes your first axis, the flagpole. Then take the second vector and strip out the part that already leans toward u1u_1:

u2=v2proju1(v2)u_2 = v_2 - \text{proj}_{u_1}(v_2)

Whatever's left has, by construction, nothing pointing along u1u_1 anymore. It's exactly perpendicular. Every time, no matter where you started.

Try it with small numbers. Let u1=(4,0)u_1 = (4, 0) and v2=(3,2)v_2 = (3, 2).

  • proju1(v2)=1216(4,0)=(3,0)\text{proj}_{u_1}(v_2) = \frac{12}{16}(4,0) = (3, 0)
  • u2=(3,2)(3,0)=(0,2)u_2 = (3,2) - (3,0) = (0, 2)
  • u1u2=4(0)+0(2)=0u_1 \cdot u_2 = 4(0) + 0(2) = 0

Now drag v2v_2 in the visualizer below and watch u2u_2 snap to a right angle from u1u_1, no matter where you drop it.

Drag v₂ anywhere. u₂ = v₂ − shadow always lands at exactly 90° from u₁.

u₁ = v₁u₂
u₁ · u₂-0.000
Angle(u₁, u₂)90.0°
|u₂|4.60

That's the whole algorithm. Everything else is just doing this move more times.


What happens with three vectors, or thirty?

We just keep going. Repeat the same move, remove the shadow, once for every axis you've already built.

For a third vector v3v_3, we subtract its shadow on u1u_1 and its shadow on u2u_2:

u3=v3proju1(v3)proju2(v3)u_3 = v_3 - \text{proj}_{u_1}(v_3) - \text{proj}_{u_2}(v_3)

Think about why both subtractions matter. If we only removed the shadow on u1u_1, the result would be perpendicular to u1u_1, but it might still lean toward u2u_2. We have to strip out every direction we've already claimed before what's left counts as genuinely new.

Watch the process unfold step by step, in three dimensions this time.

Watch the shadow-removal step chain across a third vector.

v₁v₂v₃

Start: three independent vectors

u₁ · u₂5.00
u₁ · u₃7.00
u₂ · u₃7.00

For a fourth vector, you'd subtract three shadows. For a hundredth, ninety-nine. The pattern never changes, it just repeats.


Why go through all this trouble?

Because perpendicular, unit-length vectors, what we call an orthonormal basis, don't distort anything.

Once you have orthogonal vectors u1,u2,u_1, u_2, \dots, the last step is trivial: shrink each one to length 1 by dividing by its own length.

ei=uiuie_i = \frac{u_i}{\|u_i\|}

Here's the payoff. Measure distances and angles using a skewed basis, and your ruler lies to you, a circle of "radius 1" in that basis stretches into an ellipse in reality. Measure using an orthonormal basis, and a circle stays a circle. Nothing gets stretched, squeezed, or tilted.

Drag the skew angle. The skewed basis warps the circle, the orthonormal one never does.

v₁v₂e₁e₂
Skewed area scale5.54×
Orthonormal area scale1.00×

Slide the skew angle toward 90° and watch the amber shape (the skewed basis) collapse onto the dashed reference circle. Push it toward 0° or 180° and watch it thin into a needle. The emerald shape, built from the Gram-Schmidt orthonormal basis, never budges. It's always a perfect circle, no matter the angle you started with.

That's why Gram-Schmidt matters far outside a linear algebra homework set. It's the backbone of the QR decomposition, the numerical foundation under least-squares regression, and the reason a computer can trust its own answer when it solves a system with a thousand unknowns.


Putting it all together

For vectors v1,v2,,vnv_1, v_2, \dots, v_n, the full Gram-Schmidt process is:

u1=v1u2=v2proju1(v2)u3=v3proju1(v3)proju2(v3)  uk=vki=1k1projui(vk)\begin{aligned} u_1 &= v_1 \\ u_2 &= v_2 - \text{proj}_{u_1}(v_2) \\ u_3 &= v_3 - \text{proj}_{u_1}(v_3) - \text{proj}_{u_2}(v_3) \\ &\ \ \vdots \\ u_k &= v_k - \sum_{i=1}^{k-1} \text{proj}_{u_i}(v_k) \end{aligned}

Then normalize each one: ei=ui/uie_i = u_i / \|u_i\|.


The short version

Gram-Schmidt turns any set of independent vectors into a perpendicular one, without changing what they can reach. The tool doing all the work is the projection, the "shadow" one vector casts on another. Subtract that shadow and what remains is guaranteed perpendicular. Chain the subtraction across every vector you've already processed, and the whole set falls into place, axis by axis. Normalize the lengths at the end, and you're left with a basis that measures distance and angle honestly.

All visualizations are interactive React components running entirely in your browser, using nothing beyond React. The stepper animation uses requestAnimationFrame to interpolate between each stage of the algorithm.