13. Directional derivatives and the gradient
§10.2's partial derivatives measure slope along exactly two directions — parallel to the x-axis, or parallel to the y-axis. But a surface has a slope in every direction from a point, not just those two. This lesson builds the tool that measures all of them at once, and along the way produces the single most important vector in multivariable calculus: the gradient.
The directional derivative
The rate of change of f at (x_0,y_0) in the direction of a unit vector \vec u=\langle u_1,u_2\rangle is:
D_{\vec u}f(x_0,y_0)=\lim_{h\to0}\frac{f(x_0+hu_1,y_0+hu_2)-f(x_0,y_0)}h
— exactly §2.0's difference quotient, generalized so the "step" moves in an arbitrary direction \vec u instead of purely along the x-axis. Setting x(t)=x_0+tu_1, y(t)=y_0+tu_2 (a straight-line path through the point, in direction \vec u) and applying §10.4's chain rule:
D_{\vec u}f=f_x(x_0,y_0)u_1+f_y(x_0,y_0)u_2
Partial derivatives are the special case \vec u=\vec i=\langle1,0\rangle (giving D_{\vec i}f=f_x) or \vec u=\vec j=\langle0,1\rangle (giving D_{\vec j}f=f_y) — the directional derivative is the general tool, partial derivatives are just its two most common readings.
The gradient vector
Package both partial derivatives into a single vector:
\nabla f=\langle f_x,f_y\rangle
(read "grad f" or "del f"). The directional-derivative formula above is then exactly §9.1's dot product:
D_{\vec u}f=\nabla f\cdot\vec u
This single line is why the gradient matters: it packages every possible directional derivative into one vector, and recovering the derivative in any specific direction is just a dot product with that direction's unit vector.
The gradient points toward steepest ascent
Using §9.1's geometric dot-product formula, $D_{\vec u}f=|\nabla f||\vec u|\cos\theta=|\nabla f|\cos\theta$ (since |\vec u|=1), where \theta is the angle between \nabla f and \vec u. This is maximized exactly when \cos\theta=1, i.e. when \vec u points in the same direction as \nabla f itself:
\nabla f points in the direction of steepest ascent, and |\nabla f| is the rate of increase in that direction — the maximum possible value of any directional derivative at that point.
By the identical argument with \cos\theta=-1, -\nabla f points in the direction of steepest descent. And when \vec u\perp\nabla f (\theta=90°), D_{\vec u}f=0 — moving perpendicular to the gradient produces no instantaneous change in f at all.
The gradient is perpendicular to level curves
That last fact has a striking consequence: since moving along a level curve f(x,y)=c produces zero change in f by definition (the value stays fixed at c), the direction of travel along a level curve must be exactly the direction where D_{\vec u}f=0 — which was just shown to be perpendicular to \nabla f.
The gradient is always perpendicular to the level curve passing through that point.
This connects directly back to §10.0's topographic-map picture: on a real contour map, the steepest uphill direction at any point is always perpendicular to the contour line passing through it — water runs downhill along the gradient, always crossing elevation contours at a right angle, never running parallel to one.
Doing it in Python
The gradient of f(x,y)=x^2+y^2 at (1,1), and the directional derivative in the direction of \langle3,4\rangle:
import sympy as sp
x, y = sp.symbols('x y')
f = x**2 + y**2
grad = sp.Matrix([sp.diff(f, x), sp.diff(f, y)])
grad_at_point = grad.subs({x: 1, y: 1})
print(f"gradient at (1,1) = {grad_at_point.T}")
direction = sp.Matrix([3, 4])
unit_direction = direction / sp.sqrt(direction.dot(direction))
D_u_f = grad_at_point.dot(unit_direction)
print(f"unit direction = {unit_direction.T}")
print(f"directional derivative = {D_u_f} = {float(D_u_f)}")
Confirming the gradient gives the maximum possible directional derivative, by checking several directions and comparing to |\nabla f|:
import sympy as sp
import math
x, y = sp.symbols('x y')
f = x**2 + y**2
grad_at_point = sp.Matrix([sp.diff(f, x), sp.diff(f, y)]).subs({x: 1, y: 1})
max_rate = sp.sqrt(grad_at_point.dot(grad_at_point))
print(f"|grad f| = {max_rate} = {float(max_rate):.4f} (the theoretical max)")
for angle_deg in (0, 30, 45, 60, 90, 135, 180):
theta = math.radians(angle_deg)
u = sp.Matrix([math.cos(theta), math.sin(theta)])
D_u_f = float(grad_at_point.dot(u))
print(f" direction at {angle_deg:>3}°: D_u f = {D_u_f:.4f}")
print("\nmaximum occurs exactly along the gradient's own direction")
Confirming perpendicularity between the gradient and a level curve's tangent direction:
import sympy as sp
x, y, t = sp.symbols('x y t')
f = x**2 + y**2
# parametrize the level curve x^2+y^2=2 (through the point (1,1)) as a circle
level_curve = sp.Matrix([sp.sqrt(2)*sp.cos(t), sp.sqrt(2)*sp.sin(t)])
tangent = level_curve.diff(t)
t0 = sp.pi / 4 # this parametrization passes through (1,1) at t=pi/4
grad_at_point = sp.Matrix([sp.diff(f, x), sp.diff(f, y)]).subs({x: 1, y: 1})
tangent_at_point = tangent.subs(t, t0)
print(f"gradient = {grad_at_point.T}")
print(f"tangent to level curve = {tangent_at_point.T}")
print(f"dot product = {sp.simplify(grad_at_point.dot(tangent_at_point))} (perpendicular, as predicted)")
Worked example
Find the directional derivative of f(x,y)=x^2+y^2 at (1,1) in the direction of \vec v=\langle3,4\rangle, and find the direction and rate of steepest ascent at that point.
\nabla f=\langle2x,2y\rangle\ \Longrightarrow\ \nabla f(1,1)=\langle2,2\rangle
Normalize the direction: |\vec v|=\sqrt{9+16}=5, so \hat v=\left\langle\frac35,\frac45\right\rangle.
D_{\hat v}f(1,1)=\langle2,2\rangle\cdot\left\langle\frac35,\frac45\right\rangle=\frac65+\frac85=\boxed{\frac{14}5}
Steepest ascent: direction \langle2,2\rangle (or its unit form, \left\langle\frac1{\sqrt2},\frac1{\sqrt2}\right\rangle), rate |\nabla f(1,1)|=\sqrt{4+4}=\boxed{2\sqrt2}.
Sanity check. \frac{14}5=2.8, and the maximum possible rate is 2\sqrt2\approx2.83 — the directional derivative toward \langle3,4\rangle should be close to but not exceeding the maximum, since \langle3,4\rangle points in a direction fairly close to (but not exactly aligned with) the gradient direction \langle2,2\rangle (both point generally up-and-to-the-right). 2.8<2.83 ✓, and the closeness of the two numbers reflects how nearly aligned the two directions actually are.
Your turn
1. Find \nabla f for f(x,y)=x^2y-y^3 at the point (2,1).
2. Find the directional derivative of f(x,y)=x^2y-y^3 at (2,1) in the direction \langle1,0\rangle (i.e. purely along the x-axis), and confirm it matches f_x(2,1) directly.
3. True or false: at any point where \nabla f=\vec0, every directional derivative at that point is also 0.
Solutions
1. f_x=2xy, f_y=x^2-3y^2. At (2,1): f_x=4, f_y=4-3=1.
\boxed{\nabla f(2,1)=\langle4,1\rangle}
2. \vec u=\langle1,0\rangle is already a unit vector.
D_{\vec u}f(2,1)=\langle4,1\rangle\cdot\langle1,0\rangle=4
This matches f_x(2,1)=4 exactly — confirming the concept section's claim that the directional derivative along \vec i reduces to the ordinary partial derivative f_x.
3. True. D_{\vec u}f=\nabla f\cdot\vec u for any unit vector \vec u — if \nabla f=\vec0, this dot product is 0 regardless of which direction \vec u points, since the zero vector dotted with anything is zero (§9.1). Geometrically, this is precisely a point where the surface is momentarily flat in every direction at once — exactly the kind of point §10.6's extrema classification will look for next.
Check yourself in code
For f(x,y)=x^2+y^2 at (1,1), compute the gradient, the directional derivative toward \langle3,4\rangle, and the maximum rate of increase |\nabla f|.
Print exactly this:
gradient = [2, 2]
directional derivative = 14/5
max rate of increase = 2*sqrt(2)
import sympy as sp
x, y = sp.symbols('x y')
f = x**2 + y**2
grad = sp.Matrix([sp.diff(f, x), sp.diff(f, y)])
grad_at_point = grad.subs({x: 1, y: 1})
print("gradient = ...")
direction = sp.Matrix([3, 4])
unit_direction = direction / sp.sqrt(direction.dot(direction))
D_u_f = grad_at_point.dot(unit_direction)
print("directional derivative = ...")
max_rate = sp.sqrt(grad_at_point.dot(grad_at_point))
print("max rate of increase = ...")
import sympy as sp
x, y = sp.symbols('x y')
f = x**2 + y**2
grad = sp.Matrix([sp.diff(f, x), sp.diff(f, y)])
grad_at_point = grad.subs({x: 1, y: 1})
print(f"gradient = {list(grad_at_point)}")
direction = sp.Matrix([3, 4])
unit_direction = direction / sp.sqrt(direction.dot(direction))
D_u_f = grad_at_point.dot(unit_direction)
print(f"directional derivative = {D_u_f}")
max_rate = sp.sqrt(grad_at_point.dot(grad_at_point))
print(f"max rate of increase = {max_rate}")
The directional derivative D_{\vec u}f=\nabla f\cdot\vec u generalizes partial derivatives to every possible direction at once, packaging f_x and f_y into a single gradient vector \nabla f. That vector always points toward steepest ascent, with magnitude equal to the maximum possible rate of increase, and it's always perpendicular to the level curve passing through the same point — the calculus explanation for why water always runs straight downhill across a topographic map's contour lines, never along one.
Next: using the gradient — specifically, the points where it vanishes — to locate a surface's peaks, valleys, and saddle points.