Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Building Matrix Multiplication in Python

Introduction

In our last lesson, we developed a powerful geometric intuition for matrices as operators that encode linear transformations. We saw how matrix-vector multiplication applies a transformation, and how matrix-matrix multiplication corresponds to composing successive transformations. The computational rules, while abstract, were shown to be a direct consequence of this geometric view.

Today, we transition from the "why" to the "how." Our goal is to implement matrix-vector and matrix-matrix multiplication from scratch in Python. This exercise is fundamental. By building these operations from first principles, without relying on the optimized routines of libraries like NumPy, you will solidify your understanding of the underlying mechanics. This is a crucial step for anyone interested in systems programming and performance, as it reveals precisely what high-performance libraries are abstracting away.

Representing Matrices and Vectors in Python

Before we can multiply, we need a way to store our mathematical objects. In Python, the most straightforward way to represent a vector is with a list of numbers, and a matrix with a list of lists, where each inner list represents a row.

For example, the vector and the matrix would be represented as:

v = [2, 5]
M = [ [1, 3], 
      [4, 0] ]

The number of rows in M is len(M), and the number of columns is len(M[0]).

While this is our "from scratch" approach, it's important to know that the industry standard is to use NumPy arrays. A key resource for getting comfortable with Python for linear algebra demonstrates how to define vectors and matrices, first as lists, and then using NumPy.

Python for linear algebra (for absolute beginners)

This video, "Python for linear algebra," introduces the basic tools for numerical computation in Python.

Watch the segment on defining vectors. Notice how it starts with Python lists and then transitions to NumPy arrays, explaining that lists don't behave like mathematical vectors for operations like scalar multiplication. Then, watch the section on creating matrices, which shows how to build them manually as a list of lists inside a NumPy array. This reinforces the data structure we'll be using.

The Logic of Multiplication: The Row-Column Rule

As we established, the entry in the -th row and -th column of a product matrix is the dot product of the -th row of and the -th column of .

This diagram illustrates the core operation: each element of the output matrix is the result of a dot product between a row from the first matrix and a column from the second.

Let's break this down algorithmically. Consider multiplying a matrix of size by a matrix of size .

First, a question to set up our thinking: To calculate every element in the resulting matrix , we need to iterate through its rows and columns. For each element , we then compute a dot product. How many nested loops do you think are required to implement this entire process? What is each loop responsible for?

...

The answer is three. We need:

  1. A loop to iterate over the rows of the resulting matrix (from to ). Let's use i.
  2. A loop to iterate over the columns of the resulting matrix (from to ). Let's use j.
  3. An inner loop to compute the dot product for the element . This loop sums the products of elements from row i of and column j of . It runs over the shared dimension, . Let's use k.

The following video provides an excellent walkthrough of this three-loop logic.

Matrix multiplication from scratch in Python | No NumPy, no libraries | Coding for ML [Lecture 29]

The video "Matrix multiplication from scratch in Python" breaks down the algorithmic logic before writing any code.

First, watch the review of the multiplication rule to solidify the concept of matching inner dimensions and calculating the output elements. Then, pay close attention to the segment from "how can we think about this", where the presenter derives the necessity of three nested loops (k, j, i in their notation, though the principle is the same) and explains what each loop variable tracks.

From Logic to Code: A Python Implementation

Now that we have the algorithm, we can translate it into a Python function. Our function should take two matrices (as lists of lists), check if their dimensions are compatible for multiplication, and if so, compute and return the resulting matrix.

The process will be:

  1. Extract the dimensions of the input matrices and .
  2. Assert that the number of columns in equals the number of rows in .
  3. Create a new matrix (e.g., a list of lists) of the correct output dimensions, initialized with zeros.
  4. Implement the three nested for loops (i, j, k) to populate . The core calculation will be C[i][j] += A[i][k] * B[k][j].

The same video resource walks through a complete, line-by-line implementation.

Matrix multiplication from scratch in Python | No NumPy, no libraries | Coding for ML [Lecture 29]

This part of the video translates the logic into a working Python function.

Watch the section from "implement this" to see how matrices are defined as lists of lists and how their dimensions are programmatically determined using len(). Then, watch the main implementation part from "define the zero Matrix". This segment is critical as it covers creating the result matrix, checking for dimension compatibility, and coding the triple-nested loop. The presenter also debugs a common off-by-one/indexing error, which is an instructive part of the process.

For a concise textual reference of the final algorithm, the following article provides a very clean implementation.

Matrix Multiplication from scratch in Python — AIO

This article provides several implementations of matrix multiplication, starting with the pure Python approach.

Focus on the first section, Matrix Multiplication in Python. The code block here shows the canonical three-loop implementation inside a function. Note that it uses PyTorch tensors, but the indexing a[i, k] and loop structure are identical to what you would use with lists of lists, a[i][k].

The Performance Reality: Why We Use Optimized Libraries

You have now built a working matrix multiplication function from scratch. This is a fantastic exercise for understanding the mechanics. However, in any practical application, especially in performance-sensitive fields like graphics or high-frequency trading, you would never use this implementation.

Why? Python for loops are notoriously slow. The Python interpreter has a high overhead for each iteration. The "from scratch" implementation becomes computationally infeasible for even moderately sized matrices.

Libraries like NumPy, PyTorch, and TensorFlow delegate these operations to underlying code written in C, C++, or Fortran. These low-level languages can execute the same loops orders of magnitude faster and can leverage hardware-specific optimizations (like SIMD instructions) that operate on multiple numbers at once.

The article you just read provides a stark comparison.

Matrix Multiplication from scratch in Python — AIO

Let's revisit the article to see the performance implications.

Read the sections titled This takes a very long time and Enter elementwise operations. The author shows that a pure Python implementation took over 6 seconds for a small task, while a slightly more optimized version using library functions took only 9 milliseconds—a speedup of over 600x. This demonstrates the critical importance of leveraging optimized libraries.

In NumPy, matrix multiplication is a single, highly optimized operation. You can use the function np.matmul() or, more commonly, the dedicated matrix multiplication operator @.

import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])




# Using the @ operator for matrix multiplication
C = A @ B 

print(C)



# [[19 22]
#  [43 50]]

This single line replaces our entire matmul function and runs dramatically faster.

Python for linear algebra (for absolute beginners)

This segment from "Python for linear algebra" demonstrates the standard, efficient way to perform matrix multiplication.

Watch the portion from "matrix multiplication". The presenter explains the rule for valid multiplication and then demonstrates how to use both numpy.matmul and the @ operator in Python to perform the operation efficiently.

Conclusion

In this lesson, we bridged the gap between the theory and practice of matrix multiplication. You have seen how to translate the formal row-column rule into a working Python algorithm from scratch, giving you a first-principles understanding of the computation involved.

Our key takeaways are:

  • Matrices can be represented as lists of lists in Python, allowing us to manipulate them with basic language features.
  • Matrix-matrix multiplication is algorithmically a triply nested loop that iterates through the output matrix's rows and columns, with the innermost loop computing a dot product.
  • Implementing algorithms from scratch is invaluable for understanding but is not practical for performance.
  • High-performance libraries like NumPy are essential because they execute these fundamental operations in highly optimized, low-level code, offering massive speed advantages.

In our next lesson, we will step back from matrix operations to look more closely at the properties of the vectors themselves. We will introduce the crucial concepts of linear independence, span, and basis. These ideas form the vocabulary for describing vector spaces and are essential for understanding the rank of a matrix, the nature of solutions to linear systems, and the process of dimensionality reduction.

Can't find a good explanation? Sign up and we'll make it for you

Sign up