Matrix Multiplication in Python: The Complete Decision Guide

Multiplication seems simple enough, and you probably haven’t worried about it much since you mastered your times tables years ago. But ready or not, matrices are about to upend everything you know about straightforward doubling and tripling.
In this article, we’ll define matrix multiplication from a mathematical perspective, then walk you through several options for multiplying matrices in Python. We’ll compare your choices to help you pick the right one and leave you with a few final “gotchas” to avoid. Whether you’re tracking compounding interest, bacteria growth, or rabbit populations, this multiplication article has you covered.
What is Matrix Multiplication?
Matrix multiplication is a fundamental mathematical operation that combines two matrices to produce a new matrix. To perform it, you’ll take the first row of the first matrix and multiply its individual components by the corresponding elements of the first column of the second matrix, then sum all products. Calculate the next element by doing the same for the first row of the first matrix and the second column of the second, and so on.
For example, below you can find how matrix multiplication works for two 2x2 matrices:
The output is another 2x2 matrix. You can also operate on non-square matrices, but the innermost dimensions of the matrices must match for the operation to be valid. That is, the first matrix must have the same number of columns as the number of rows in the second matrix. For example, you can find the product of a 2x3 matrix and a 3x4 matrix; the result will take on the outer dimensions, 2x4.
Method 1: Nested Loops to Multiply Two Matrices
You can use several different Python approaches to implement matrix multiplication. First, you may use nested loops for a pure Python approach that doesn’t require any external libraries.
To do so, you’ll build your two matrices as nested lists and initialize your result matrix with all zeros. Then build three nested loops:
Outer loop: rows of the first matrix
Middle loop: columns of the second matrix
Inner loop: multiplying corresponding row/column elements and adding the products
This code multiplies the matrices from our initial example using three nested for loops:
While for loops offer a great way to learn about the inner workings of matrix multiplication, they produce relatively verbose code and aren’t the most efficient technique for multiplying large matrices.
Method 2: Nested List Comprehension
For an alternative pure Python approach that requires less code, you could try nested list comprehensions. This method computes the same output as the nested for loops, but is more Pythonic and does not require initialization of the result matrix.
This is what the 2x2 matrix example looks like with nested list comprehensions. Notice how we rely on zip() to pair the corresponding row and column elements, zip(*B) to access each column vector of the second matrix, and sum() to add the pairwise products:
While less verbose, this technique proves a bit harder to read for beginners. It’s also still not particularly efficient for heavy-duty matrix calculations.
Method 3: NumPy dot()
For high-performance matrix multiplication, you’ll likely want to utilize the NumPy library. NumPy is an external library for fast, efficient numerical calculations and scientific computing. You’ll need to install it separately and import it each time you use it.
You’ll now create your two matrices as NumPy arrays. You can multiply them using NumPy’s np.dot() function. This function automatically handles the necessary row-column products and summations:
This technique provides a highly concise, readable way to multiply arrays. It also proves to be much better suited to multiply large matrices efficiently. You will still need matrices with compatible dimensions to apply this function. You’ll receive a ValueError if the innermost dimensions of your matrices don’t match.
Dot Product and Scalar Multiplication
np.dot() offers different functionality depending on its inputs. If you supply two vectors (1-D arrays) to np.dot(), you’ll receive the dot product of those vectors as the output. The dot product works just like computing a single entry of matrix multiplication. Think of the first input vector as a row vector, while the second input vector is a column vector. Your output will be a single scalar value obtained by summing all the corresponding element products.
And this is what the code looks like using np.dot() function:
You can also achieve scalar multiplication of a matrix with the np.dot() function if you input a matrix and a scalar value. Say, you’d like to scale your matrix A by a factor of 2; that is, you want to multiply every element of A by 2. We demonstrate how that works with np.dot() in the next example:
Method 4: NumPy matmul() / @ Operator for the Matrix Product
While np.dot() works perfectly well for two-dimensional arrays, developers typically prefer another NumPy function, np.matmul(), when specifically multiplying matrices.
Here’s np.matmul() in action:
np.matmul() gives the same result as np.dot() for standard matrix multiplication, but the two functions differ when working with certain other input dimensions. Unlike np.dot(), np.matmul() does not accept scalar inputs, and its behavior for higher-dimensional arrays, sometimes called tensors, follows broadcasting rules. Its intent is, therefore, more straightforward when you specifically want matrix multiplication.
Python introduced the @ operator in version 3.5 specifically for matrix multiplication. It performs the same operation as np.matmul() with cleaner syntax. You can rewrite the multiplication line in the code above as:
This proves particularly helpful when chaining products. If you want to multiply the output by another matrix C, you need only type A @ B @ C.
Comparison Table of Matrix Multiplication
With so many ways to multiply two matrices, you may be confused about which method to choose. Below you’ll find a handy comparison table that lists each technique along with its typical use case, strengths, and weaknesses.
Method | Library | Best Use Case | Strengths | Weaknesses |
|---|---|---|---|---|
Nested loops | None | Learning how matrix multiplication works; very small matrices | Pure Python; transparent logic; easy to trace | Verbose; slow for large matrices |
Nested list comprehensions | None | Small matrices | Shorter than explicit loops; no result initialization | Harder to read; inefficient for large matrices |
np.dot() | NumPy | General numerical work; matrix multiplication alongside dot products | Concise; fast; supports several input types | Behavior changes with dimensionality; less explicit intent |
np.matmul() | NumPy | Matrix multiplication, especially with higher-dimensional arrays | Clear matrix multiplication; broadcasting support | Requires NumPy; no scalar inputs |
@ operator | None, but inputs should be NumPy arrays or comparable | Readable, concise matrix multiplication | Clean syntax; easy to chain; explicit intent | Requires objects that implement @; may be less familiar to beginners |
Common Errors and Fixes in Python Programs
Python makes matrix multiplication feel simple, especially with syntax like the @ operator. There are a few details to pay special attention to, however. Here’s a quick summary of the most common issues you’ll see in practice.
Dimension / Shape Mismatch
The most common error happens when the innermost dimensions of your matrices don’t match. The number of columns in the initial matrix must match the number of rows in the second for the operation to be valid. For two general matrices, the shapes should follow this pattern:
(m x n) @ (n x p) → (m x p)
For example, your matrices could be (3 x 2) @ (2 x 4) → (3 x 4). These, however, are invalid: (2 x 3) @ (2 x 4), since the innermost dimensions aren’t the same.
Unlike scalar multiplication, matrix multiplication is not commutative. In general A @ B ≠ B @ A. Furthermore, B @ A may not even be valid while A @ B is.
You may receive a ValueError error for mismatched shapes in NumPy. The exact message depends on your NumPy version and the function or operator you’re using, but could say “shapes not aligned.” Pure Python implementations may not provide a helpful dimension mismatch error. You might receive an IndexError or even calculate an incorrect output without an exception.
To avoid or correct this issue, double-check your matrix dimensions. Use the .shape attribute for NumPy arrays, or try len(A) for row counts and len(A[0]) to count columns if working with nested lists.
dtype Issues
NumPy arrays generally use a single data type (dtype) for all their elements. NumPy matrix multiplication generally requires numeric values, and it may raise an error for non-numeric data.
NumPy attempts to do helpful dtype conversion when it encounters a mixed type array. Nonetheless, you may encounter errors if you have numeric values stored as strings.
Fix this problem by first checking the .dtype attribute to view the data type of your array. You may then make data type adjustments with the .astype() method (e.g. A.astype(float)). Also keep in mind that integers and floats differ in how they represent values and handle numerical precision.
Confusing * and @
You may already be familiar with Python's multiplication operator, *, to take the product of scalar values (e.g. 2 * 3 → 6). It turns out that you can use * to do element-wise multiplication for NumPy arrays. This operator pairs each array value with the corresponding element in a second array and multiplies them. It does not do summation over rows or columns:
The @ operator, however, specifically multiplies matrices. You’ll likely receive an entirely different result when swapping these operators:
Switching * and @ commonly leads to silent errors in Python programs when both operations are valid, so be sure to use @ for matrix multiplication and save * for elementwise or scalar multiplication.
Broadcasting for Matrix Multiplication
As a final consideration, NumPy can operate on arrays with different but compatible dimensions using a technique called broadcasting. This allows calculations between smaller and larger arrays without the need for copying or reshaping the data.
For example, when adding a row vector b to matrix A, NumPy assumes you want to add b to each row of matrix A:
Broadcasting typically becomes relevant when taking the product of arrays with 3 or more dimensions (tensors). Both np.matmul() and the @ operator treat the final two axes as matrices, while considering earlier axes like batches of matrices. This becomes highly useful for performing the same matrix operation across many matrices, as in some machine learning algorithms.
Wrapping Up
Matrix multiplication is a common mathematical operation that combines two matrices into one output matrix. Python offers many different methods to achieve this including pure Python techniques like nested loops and list comprehensions, NumPy functions (np.dot() and np.matmul()), and the @ operator. To avoid the most common pitfalls, double-check your matrix dimensions and data types, and make sure you aren’t confusing the multiplication operator, *, with the matrix multiplication operator, @.
Next, make sure you’ve got multiplication down pat by interacting with the AI Tutor. This prompt will get you started by clearing up any lingering confusion about * and @:
Kimberly Fessel