← all writing

Why gradient descent zigzags

A whiteboard-sized note on conditioning, with a toy you can poke at.

3 min read

  • optimization
  • notes

Gradient descent on a quadratic is the “hello world” of optimization, and it already contains the most important idea in the subject: the shape of the loss matters more than its size. Here is the whole argument, small enough to fit on one whiteboard.

The setup

Take a quadratic f(x)=12x⊤Axf(x) = \tfrac{1}{2} x^\top A x with AA symmetric positive definite, and call its smallest and largest eigenvalues μ\mu and LL. The gradient is ∇f(x)=Ax\nabla f(x) = Ax, so a step of gradient descent with step size η\eta is

xk+1=xk−η∇f(xk)=(I−ηA) xk.(1)x_{k+1} = x_k - \eta \nabla f(x_k) = (I - \eta A)\, x_k. \tag{1}

Now write xx in the eigenbasis of AA.This is the whole trick. In the eigenbasis the problem splits into independent one-dimensional problems, one per eigenvalue, and each one is trivial. Each coordinate evolves on its own, shrinking by a fixed factor at every step:

xk(i)=(1−ηλi)k x0(i).x^{(i)}_k = (1 - \eta \lambda_i)^k \, x^{(i)}_0 .

So we converge only if ∣1−ηλi∣<1|1 - \eta \lambda_i| < 1 for every eigenvalue, which means η<2/L\eta < 2/L.Push η\eta past 2/L2/L in the toy below and watch the red path leave the board. The slowest coordinate sets the pace, and the best we can do is to balance the two extremes with η⋆=2/(L+μ)\eta^\star = 2/(L + \mu), giving the rate

ρ⋆=L−μL+μ=κ−1κ+1,κ=Lμ.\rho^\star = \frac{L - \mu}{L + \mu} = \frac{\kappa - 1}{\kappa + 1}, \qquad \kappa = \frac{L}{\mu}.

The condition number κ\kappa is everything. When κ=1\kappa = 1 the contours are circles, the negative gradient points straight at the minimum, and a single step of size 1/L1/L lands on it. When κ\kappa is large the contours are long, thin ellipses. The gradient is perpendicular to the contour, not aimed at the minimum, so it mostly points across the valley: we bounce between the walls while creeping along the floor.

∇f ⟂ contours,not aimed at the min κ ≈ 1: one straight shotκ ≫ 1: bouncing off the walls
Same algorithm, different bowls. Only the shape of the contours changed.

Play with it

The toy below runs both methods on a quadratic with κ=16\kappa = 16. Click anywhere on the board to restart from that point.

gradient descent momentum
Plain gradient descent (red) zigzags across the valley. Heavy-ball momentum (blue) carries velocity along the floor and gets there first. Try η>0.125\eta > 0.125.

Momentum

Polyak’s heavy-ball method adds a fraction of the previous step:think: a ball rolling in the bowl, not a hiker taking careful steps

xk+1=xk−η∇f(xk)+β (xk−xk−1).x_{k+1} = x_k - \eta \nabla f(x_k) + \beta \,(x_k - x_{k-1}).

With η\eta and β\beta tuned to the curvature, the rate improves from (κ−1)/(κ+1)(\kappa - 1)/(\kappa + 1) to (κ−1)/(κ+1)(\sqrt{\kappa} - 1)/(\sqrt{\kappa} + 1).Polyak’s tuning: η=4/(L+μ)2\eta = 4/(\sqrt{L} + \sqrt{\mu})^2 and β=ρ2\beta = \rho^2, where ρ\rho is that new rate. That square root is not a small detail. With κ=100\kappa = 100:

methodrate per stepsteps to shrink the error 106×10^6\times
gradient descent99/101≈0.98099/101 \approx 0.980≈690\approx 690
heavy ball9/11≈0.8189/11 \approx 0.818≈69\approx 69

A tenfold speed-up for one extra vector of memory. The whole thing is a few lines:

import numpy as np

def heavy_ball(A, x0, lr, beta, steps):
    x, prev = x0, x0
    for _ in range(steps):
        x, prev = x - lr * (A @ x) + beta * (x - prev), x
    return x

The takeaway

Much of what makes modern optimizers work, including momentum, preconditioning and Adam’s per-coordinate step sizes, can be read as an attempt to make κ\kappa smaller, or to stop caring about it. The zigzag is what it looks like when you don’t.

Cite this post
@misc{prakash2026gradient,
  author = {Arya Prakash},
  title  = {Why gradient descent zigzags},
  year   = {2026},
  month  = {oct},
  url    = {https://jirachi.ai/posts/why-gradient-descent-zigzags/},
}