Linear Transformations

Plenty of functions "move points around" — stretch a photo, warp a map, bend a curve. But not every one of them can ever be written as a matrix. Before the whole matrix toolkit (composing transformations, inverting them, finding eigenvalues) can be pointed at a transformation, that transformation has to earn the right by passing a strict gatekeeper test.

Stop thinking of a matrix as a table of numbers, and start thinking of it as an action: a rule that takes in a vector and moves it to a new one, transforming the entire plane at once. A linear transformation is a special, very well-behaved kind of motion. It must keep two promises, no exceptions:

In symbols, a transformation T is linear exactly when it respects both addition and scaling for every pair of vectors \vec u, \vec v and every scalar c:

T(\vec{u}+\vec{v}) = T(\vec{u}) + T(\vec{v}) \qquad \text{and} \qquad T(c\vec{v}) = c\,T(\vec{v}).

No bending, no curving, no shifting off the origin — just a clean stretch-and-skew of space. The rest of this page is about learning to check those two promises, rather than trusting a transformation just because it looks tidy.

The two promises, one at a time

AdditivityT(\vec u + \vec v) = T(\vec u) + T(\vec v) — says it doesn't matter whether you add two vectors first and then transform the sum, or transform each one separately and add the results after: you land in the same place either way. Picture the parallelogram with \vec u and \vec v as two edges — its far corner is \vec u+\vec v. Additivity says applying T to the whole parallelogram (edges and all) just gives another parallelogram, built the same way from T(\vec u) and T(\vec v).

Scaling (also called homogeneity) — T(c\vec v) = c\,T(\vec v) — says stretching a vector by a factor c before transforming it gives the same answer as transforming it first and then stretching the result by c. Geometrically: a whole line of evenly spaced points through the origin (multiples of \vec v) lands on another line of evenly spaced points through the origin — never bunched up, never spread apart unevenly, never bent into a curve.

Put the two together and you get the picture from the definition: a grid of straight, parallel, evenly spaced lines through the origin must land as another grid of straight, parallel, evenly spaced lines through the origin. That rigidity is the whole reason a single matrix — just a handful of numbers — can capture the entire transformation.

Watch space deform

Drag the slider to apply a transformation gradually. The grid stretches and shears — yet every line stays a line, parallel lines stay parallel, and the centre never moves. Watch the two coloured arrows: they track where the "unit" directions (1,0) and (0,1) end up, and once you know that, additivity and scaling pin down everywhere else — you're watching the two defining properties in action, not just a pretty animation.

Worked example: checking a scaling map

Let T(x,y) = (2x, 2y) — every point moves twice as far from the origin. Is it linear? Test it on two arbitrary vectors \vec u = (u_1,u_2) and \vec v=(v_1,v_2), rather than trusting a guess.

Additivity:

T(\vec u+\vec v) = T(u_1+v_1,\ u_2+v_2) = \big(2(u_1+v_1),\ 2(u_2+v_2)\big) = (2u_1+2v_1,\ 2u_2+2v_2). T(\vec u)+T(\vec v) = (2u_1,2u_2) + (2v_1,2v_2) = (2u_1+2v_1,\ 2u_2+2v_2).

The two sides match exactly, for any choice of \vec u,\vec v — not just a lucky pair of numbers.

Scaling: T(c\vec v) = (2cv_1, 2cv_2) = c(2v_1,2v_2) = c\,T(\vec v). Both promises hold, so doubling is linear.

A concrete check with numbers: take \vec u=(1,3) and \vec v=(4,-1). Then \vec u+\vec v=(5,2), so T(\vec u+\vec v) = (10,4). Separately, T(\vec u)=(2,6) and T(\vec v)=(8,-2), and their sum is (10,4) too — matching, as the general argument promised.

Worked example: checking a 90° rotation

Rotating every point 90° counter-clockwise around the origin sends (x,y) to (-y,x). Test it on \vec u=(1,0) and \vec v=(0,1), then on their sum.

T(\vec u)=(0,1), \qquad T(\vec v)=(-1,0), \qquad T(\vec u)+T(\vec v)=(-1,1). \vec u+\vec v=(1,1), \qquad T(\vec u+\vec v)=(-1,1).

They agree. Rotation also preserves scaling: shrinking \vec v to \tfrac12\vec v=(0,0.5) and rotating gives (-0.5,0), exactly \tfrac12 of T(\vec v)=(-1,0). A rotation never bends a line and it always fixes the centre it spins around — so it was never in doubt, but now it's proven, not assumed.

Worked example: a transformation that fails — translation

Now test a transformation that looks perfectly innocent: sliding every point 3 units right and 2 units up, T(x,y) = (x+3,\ y+2). Before checking either property, apply the fastest possible test — does the origin stay fixed?

T(0,0) = (0+3,\ 0+2) = (3,2) \neq (0,0).

That single computation is already fatal: a linear map is required to send the zero vector to the zero vector (put c=0 into the scaling rule: T(\vec 0)=T(0\cdot\vec v)=0\cdot T(\vec v)=\vec 0). Translation fails before we even reach additivity. For the record, additivity fails too — take \vec u=(1,0) and \vec v=(0,1):

T(\vec u+\vec v) = T(1,1) = (4,3), \quad\text{but}\quad T(\vec u)+T(\vec v) = (4,2)+(3,3) = (7,5).

(4,3) \neq (7,5). Sliding the whole plane sideways is a perfectly reasonable thing to want to do to a picture — it's just not a linear thing to do. (It has its own name, an affine transformation, and its own page.)

Worked example: a transformation that fails both properties badly

Squaring each coordinate, T(x,y) = (x^2, y^2), at least fixes the origin — T(0,0)=(0,0) — so the fast test alone can't catch it. Check additivity directly with \vec u=(1,0) and \vec v=(1,0), so \vec u+\vec v = (2,0):

T(\vec u+\vec v) = T(2,0) = (4,0), \qquad T(\vec u)+T(\vec v) = (1,0)+(1,0) = (2,0).

4 \neq 2 — additivity is broken. Scaling fails too: with c=2, T(2\vec u) = T(2,0) = (4,0), but 2\,T(\vec u) = 2(1,0) = (2,0). Squaring bends every straight line (except the axes) into a curve, so it was always going to fail — but notice that fixing the origin was not enough on its own to guarantee linearity. Both promises have to be checked, every time.

// A tiny "linearity checker": tests additivity + scaling on random vectors. function isLinear(T: (v: [number, number]) => [number, number]): boolean { const u: [number, number] = [1.7, -2.3]; const w: [number, number] = [-0.4, 3.1]; const c = 2.5; const sum: [number, number] = [u[0] + w[0], u[1] + w[1]]; const [Tu0, Tu1] = T(u); const [Tw0, Tw1] = T(w); const [Tsum0, Tsum1] = T(sum); const additive = close(Tsum0, Tu0 + Tw0) && close(Tsum1, Tu1 + Tw1); const scaled: [number, number] = [c * u[0], c * u[1]]; const [Tscaled0, Tscaled1] = T(scaled); const homogeneous = close(Tscaled0, c * Tu0) && close(Tscaled1, c * Tu1); return additive && homogeneous; } function close(a: number, b: number): boolean { return Math.abs(a - b) < 1e-9; } console.log("doubling: ", isLinear(([x, y]) => [2 * x, 2 * y])); console.log("rotate 90: ", isLinear(([x, y]) => [-y, x])); console.log("translate: ", isLinear(([x, y]) => [x + 3, y + 2])); console.log("square each: ", isLinear(([x, y]) => [x * x, y * y]));

What stays linear, what doesn't

Rotations, stretches, reflections and shears are all linear — every one of them passes both tests. Bending a line into a curve is not linear; nor is sliding the whole plane sideways (that moves the origin — it's an affine move). Because linear maps preserve the grid completely, they're pinned down entirely by where just two arrows go, T(1,0) and T(0,1) — the subject of the very next page.

A transformation can look deceptively simple and still fail. "Add 5 to every coordinate," "shift everything sideways," "always output (1,1) no matter what goes in" — all sound like plausible candidates, and none of them are linear. The only honest way to know is to run both tests, on general vectors, not just a single convenient example:

A single numerical example that happens to work is not a proof — it might just be a coincidence for that pair of vectors. Checking on general symbols (like (u_1,u_2) and (v_1,v_2), as in the worked examples above) is what actually settles it.

If matrices can never translate, how does every 3-D game, CAD program and animation engine slide objects around the screen constantly? With a clever bit of smuggling. Bolt one extra "fake" coordinate onto every vector — turn (x,y) into (x,y,1) — and suddenly a bigger matrix, acting linearly on the padded 3-D vectors, can produce an effect that looks exactly like translation when you read off just the first two coordinates. Translation sneaks in through the back door of a higher-dimensional linear map. That trick is called homogeneous coordinates, and it's the reason graphics pipelines can treat rotation, scaling and sliding as "just matrix multiplication" underneath it all.

A concrete taste: to slide every point 3 right and 2 up, pad each 2-D point (x,y) with a third coordinate fixed at 1, then multiply by a bigger, genuinely linear 3\times3 matrix:

\begin{bmatrix} 1 & 0 & 3 \\ 0 & 1 & 2 \\ 0 & 0 & 1 \end{bmatrix}\begin{bmatrix} x \\ y \\ 1 \end{bmatrix} = \begin{bmatrix} x+3 \\ y+2 \\ 1 \end{bmatrix}.

Read off just the first two numbers of the result and you've got exactly the translation that failed the linearity test earlier on this page — except this time it's riding inside a matrix that genuinely does fix its own (3-D) origin and keep its own grid lines straight. Nothing about the rules of linearity was broken; the trick was to change which space you're secretly working in.

See it explained