How Matrix Multiplication Works
Step-by-step mechanics of matrix multiplication: the (m, n) @ (n, p) → (m, p) shape rule and how every cell is a row-times-column dot product.
Matrix multiplication looks magical until you do one by hand. Once you see that every cell of the answer is just "multiply matching numbers and add them up", it becomes easy — and you will be able to predict the shape of any result before running the code.
Definition
For a matrix A of shape
(m, n)and a matrix B of shape(n, p), the matrix productC = A @ Bhas shape(m, p), and each entry isC[i, j] = A[i, 0]*B[0, j] + A[i, 1]*B[1, j] + ... + A[i, n-1]*B[n-1, j]— the dot product of rowiof A with columnjof B.
- Inner dimensions (
nandn) — the columns of A and the rows of B. They must be equal, because each row of A is paired number-by-number with a column of B. - Outer dimensions (
mandp) — they become the shape of the result. - Dot product — multiply matching positions and add (covered in its own topic).
The shape rule
text
A (2, 3) @ B (3, 2) -> C (2, 2)
└──── must match ────┘
└──────── become the result ───────┘| A shape | B shape | Allowed? | Result shape |
|---|---|---|---|
| (2, 3) | (3, 2) | ✅ 3 = 3 | (2, 2) |
| (3, 2) | (2, 3) | ✅ 2 = 2 | (3, 3) |
| (5, 3) | (3, 1) | ✅ | (5, 1) |
| (5, 3) | (3,) 1D | ✅ | (5,) |
| (2, 3) | (2, 3) | ❌ 3 ≠ 2 | error — try A @ B.T |
| (1, 4) | (4, 1) | ✅ | (1, 1) |
Syntax
python
C = A @ B # or np.matmul(A, B) / np.dot(A, B)
A.T # transpose: swaps rows and columns, often used to fix shapes| Item | Meaning | Note |
|---|---|---|
A | left matrix, shape (m, n) | number of columns = n |
B | right matrix, shape (n, p) | number of rows must also be n |
C | result, shape (m, p) | one cell per (row of A, column of B) pair |
A.T | transpose of A, shape (n, m) | turns rows into columns |
Example — by hand, then with NumPy
text
A (2x3) B (3x2)
[ 1 2 3 ] [ 7 8 ]
[ 4 5 6 ] [ 9 10 ]
[ 11 12 ]
C[0,0] = row 0 of A · col 0 of B = 1*7 + 2*9 + 3*11 = 7 + 18 + 33 = 58
C[0,1] = row 0 of A · col 1 of B = 1*8 + 2*10 + 3*12 = 8 + 20 + 36 = 64
C[1,0] = row 1 of A · col 0 of B = 4*7 + 5*9 + 6*11 = 28 + 45 + 66 = 139
C[1,1] = row 1 of A · col 1 of B = 4*8 + 5*10 + 6*12 = 32 + 50 + 72 = 154python
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6]])
B = np.array([[7, 8],
[9, 10],
[11, 12]])
result = np.zeros((2, 2), dtype=int)
for i in range(2): # row of A
for j in range(2): # column of B
result[i, j] = np.sum(A[i, :] * B[:, j])
print(f"result[{i},{j}] = {A[i, :].tolist()} . {B[:, j].tolist()} = {result[i, j]}")
print(result)
print(A @ B)text
result[0,0] = [1, 2, 3] . [7, 9, 11] = 58
result[0,1] = [1, 2, 3] . [8, 10, 12] = 64
result[1,0] = [4, 5, 6] . [7, 9, 11] = 139
result[1,1] = [4, 5, 6] . [8, 10, 12] = 154
[[ 58 64]
[139 154]]
[[ 58 64]
[139 154]]How it works: the double loop is exactly the textbook definition: A[i, :] is a whole row, B[:, j] is a whole column, multiply them element-wise and sum. A @ B does the same thing in optimised C/BLAS code — thousands of times faster on large matrices. Never write the loop in real code; it is here only to show the mechanics.
Example — order changes the shape
python
print("A @ B shape:", (A @ B).shape)
print("B @ A shape:", (B @ A).shape)
print(B @ A)text
A @ B shape: (2, 2)
B @ A shape: (3, 3)
[[ 39 54 69]
[ 49 68 87]
[ 59 82 105]]Both products are legal here, but (3, 2) @ (2, 3) gives a (3, 3) matrix — a totally different result.
Example — when shapes don't match, transpose
python
try:
A @ A
except ValueError as e:
print("ValueError:", e)
print(A @ A.T)text
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)
[[14 32]
[32 77]](2, 3) @ (2, 3) fails (3 ≠ 2). A.T has shape (3, 2), so (2, 3) @ (3, 2) works and gives (2, 2). Read the error as: "the k of the second operand is 2 but should be 3".
Chaining
text
(5, 3) @ (3, 1) -> (5, 1)
(5, 3) @ (3, 1) @ (1, 4) -> (5, 4)
(5, 3) @ (3,) -> (5,) # a 1D vector on the right acts like a columnCommon mistakes
- ❌ Checking that A and B have the same shape. ✅ Check that columns of A = rows of B.
- ❌ Assuming the result has the shape of A. ✅ Result shape = (rows of A, columns of B).
- ❌ Writing nested Python loops for matrix products. ✅
A @ B— it uses highly optimised BLAS routines. - ❌ Transposing randomly until the error goes away. ✅ Write the shapes down and decide which axis must match.
Interview tip
Q: What is the shape of (4, 7) @ (7, 2) @ (2, 9)?
A: (4, 7) @ (7, 2) → (4, 2); then (4, 2) @ (2, 9) → (4, 9). Inner dimensions cancel, outer ones remain.
Key points to remember
- (m, n) @ (n, p) → (m, p): inner dimensions must match, outer dimensions form the result.
- Each result cell C[i, j] is the dot product of row i of A and column j of B.
- Order matters: (2, 3) @ (3, 2) gives (2, 2) but (3, 2) @ (2, 3) gives (3, 3).
- When shapes don't line up, a transpose (A.T) often fixes it — but think about what it means.
- A 1D vector on the right of @ acts like a column and the result is 1D.
- Never write loops for matrix products; @ uses optimised BLAS code.
Try it yourself
Matrix multiplication by hand vs @
Predict the shape
Practice: fix the shape error
Revision Flashcards
Tap a card to flip it and see the answer.
