NumPy matrix multiplication: Get started in 5 minutes

NumPy matrix multiplication: Get started in 5 minutes

10 mins read
Apr 30, 2026
Share
editor-page-cover

NumPy is a popular Python library that offers a range of powerful mathematical functions. The library is widely used in quantitative fields, such as data science, machine learning, and deep learning. We can use NumPy to perform complex mathematical calculations, such as matrix multiplication.

NumPy matrix multiplication can help give us quick approximations of very complicated calculations. It can help us with network theory, linear systems of equations, population modeling, and much more. In this tutorial, we’ll explore some basic computations with NumPy matrix multiplication and matrix operations.

Let’s get started!

We’ll cover:

Learn to code today.#

Try one of our courses on Python programming fundamentals:

What is NumPy?#

NumPy is an open-source Python library that we can use to perform high-level mathematical operations with arrays, matrices, linear algebra, Fourier analysis, and more. The NumPy library is very popular in scientific computing, data science, and machine learning. NumPy is compatible with various data types and other popular data science libraries like pandas, matplotlib, and Scikit-learn. It’s much faster than Python lists because it integrates faster codes, such as C and C++, in Python. It also breaks down our tasks into multiple pieces and processes each piece concurrently.

Installing and importing NumPy#

Before we get started, let’s make sure we have NumPy installed. If you already have Python, you can install NumPy with one of the following commands:

Python 3.8
conda install numpy

or

Python 3.8
pip install numpy

To import NumPy into our Python code, we can use the following command:

Python 3.8
import numpy as np

If you’re looking to learn Python data structures, start off with this helpful Python tutorial!


What is a NumPy matrix?#

A matrix is a 2-D array. Each element in the array has two indices. Let’s look at an example in NumPy:

Python 3.8
import numpy as np
A = [[6, 7],
[8, 9]]
print(np.array(A) [0,0])

In the above code, we have matrix A [[6, 7], [8, 9]]. We ask for the element given at (0,0), and our output returns 6. When we want to define the shape of our matrix, we use the number of rows by the number of columns. That means that matrix A has a shape of 2x2.

​Now, let’s take a look at some different NumPy matrix multiplication methods.

NumPy matrix multiplication methods#

There are three main ways to perform NumPy matrix multiplication:

  • np.dot(array a, array b): returns the scalar or dot product of two arrays
  • np.matmul(array a, array b): returns the matrix product of two arrays
  • np.multiply(array a, array b): returns the element-wise matrix multiplication of two arrays
Method What it does Works with (dimensions) Handles broadcasting? Readability Typical use case When to use
np.dot() Performs dot product or matrix multiplication depending on input 1D, 2D, and higher (behavior varies) Limited and sometimes confusing Medium Vector dot products, legacy matrix code Use when working with simple vectors or maintaining older code
np.matmul() Performs matrix multiplication only 2D and higher (supports stacked matrices) Yes (designed for broadcasting stacks) High Matrix multiplication, batch operations on matrices Use when you want clear, predictable matrix multiplication behavior
@ operator Python shorthand for np.matmul() Same as np.matmul() Yes Very high (most readable) Clean, modern matrix operations in Python code Use in most cases for better readability and cleaner syntax

When should you use each?#

  • np.dot() → Use this for simple dot products (like vector operations) or when working with older NumPy codebases.
  • np.matmul() → Use this when you want explicit and reliable matrix multiplication, especially with higher-dimensional arrays.
  • @ operator → Use this in most modern Python code. It’s cleaner, easier to read, and does exactly what matmul does.

Tip: Don’t confuse these with element-wise multiplication (*). Matrix multiplication (@, matmul, dot) follows linear algebra rules, while * multiplies elements one by one.

Let’s take a closer look at each of the three methods with a NumPy tutorial of each:

Scalar multiplication or dot product with numpy.dot#

Scalar multiplication is a simple form of matrix multiplication. A scalar is just a number, like 1, 2, or 3. In scalar multiplication, we multiply a scalar by a matrix. Each element in the matrix is multiplied by the scalar, which makes the output the same shape as the original matrix.

With scalar multiplication, the order doesn’t matter. We’ll get the same result whether we multiply the scalar by the matrix or the matrix by the scalar.

Let’s take a look at an example:

Python 3.8
import numpy as np
A = 5
B = [[6, 7],
[8, 9]]
print(np.dot(A,B))

Now, let’s multiply a 2-dimensional matrix by another 2-dimensional matrix. When multiplying two matrices, the order matters. That means that matrix A multiplied by matrix B is not the same as matrix B multiplied by matrix A.

Before we get started, let’s look at a visual for how the multiplication is done.

Python 3.8
import numpy as np
A = [[6, 7],
[8, 9]]
B = [[1, 3],
[5, 7]]
print(np.dot(A,B))
print("----------")
print(np.dot(B,A))

Note: It’s important to note that we can only multiply two matrices if the number of columns in the first matrix is equal to the number of rows in the second matrix.

Learn to code today.#

Try one of our courses on Python programming fundamentals:

Matrix product with numpy.matmul#

The matmul() function gives us the matrix product of two 2-d arrays. With this method, we can’t use scalar values for our input. If one of our arguments is a 1-d array, the function converts it into a NumPy matrix by appending a 1 to its dimension. This is removed after the multiplication is done.

If one of our arguments is greater than 2-d, the function treats it as a stack of matrices in the last two indexes. The matmul() method is great for times when we’re unsure of what the dimensions of our matrices will be.

Let’s look at some examples:

Multiplying a 2-d array by another 2-d array

Python 3.8
import numpy as np
A = [[2, 4],
[6, 8]]
B = [[1, 3],
[5, 7]]
print(np.matmul(A,B))

Multiplying a 2-d array by a 1-d array

Python 3.8
import numpy as np
A = [[5, 0],
[0, 5]]
B = [5, 2]
print(np.matmul(A,B))

One array with dimensions greater than 2-d

Python 3.8
import numpy as np
A = np.arange(8).reshape(2, 2, 2)
B = np.arange(4).reshape(2, 2)
print(np.matmul(A,B))

Element-wise matrix multiplication with numpy.multiply#

The numpy.multiply() method takes two matrices as inputs and performs element-wise multiplication on them. Element-wise multiplication, or Hadamard Product, multiples every element of the first NumPy matrix by the equivalent element in the second matrix. When using this method, both matrices should have the same dimensions.

Let’s look at an example:

Python 3.8
import numpy as np
A = np.array([[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]])
B = np.array([[1, 2, 3, 4, 5], [5, 4, 3, 2, 1]])
print(np.multiply(A,B))

We can pass certain rows, columns, or submatrices to the numpy.multiply() method. The sizes of the rows, columns, or submatrices that we pass as our operands should be the same. Let’s look at an example:

Python 3.8
import numpy as np
A = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
B = np.array([[11, 12, 13, 14, 15], [16, 17, 18, 19, 20]])
print(np.multiply(A[ 0,:], B[ 1,: ]))
print("----------")
print(np.multiply(A[ 1,:], B[ 0,:]))

Understanding NumPy broadcasting#

Broadcasting is what makes NumPy feel “smart” when working with arrays of different shapes. It allows NumPy to perform operations by stretching one array so it matches the shape of another—without actually copying data.

Core broadcasting rules#

To understand when broadcasting works (and when it fails), keep these rules in mind:

  • Rule 1: Compare shapes from right to left (last dimension first)

  • Rule 2: Dimensions are compatible if:

    • They are equal, OR

    • One of them is 1

  • Rule 3: If dimensions are not compatible, NumPy raises an error

  • Rule 4: Missing dimensions are treated as 1

  • Rule 5: The smaller array is “stretched” (not copied) to match the larger shape

Example 1: Vector + scalar#

import numpy as np
a = np.array([1, 2, 3]) # shape (3,)
b = 10 # scalar
result = a + b
print(result)

Shapes:#

  • a: (3,)

  • b: () → treated as (1,)

  • Output: (3,)

NumPy stretches the scalar 10 across all elements.

Example 2: Matrix + vector (row-wise)#

a = np.array([[1, 2, 3],
[4, 5, 6]]) # shape (2, 3)
b = np.array([10, 20, 30]) # shape (3,)
result = a + b
print(result)

Shapes:#

  • a: (2, 3)

  • b: (3,) → treated as (1, 3)

  • Output: (2, 3)

The vector is broadcast across each row of the matrix.

Example 3: Element-wise multiplication#

a = np.array([[1],
[2],
[3]]) # shape (3, 1)
b = np.array([10, 20, 30]) # shape (3,)
result = a * b
print(result)

Shapes:#

  • a: (3, 1)

  • b: (3,) → treated as (1, 3)

  • Output: (3, 3)

NumPy stretches both arrays to produce a full 3×3 result.

Why this matters#

Broadcasting removes the need for manual loops, which makes your code both cleaner and faster. Once you understand these rules, many NumPy operations—especially matrix and element-wise operations—start to feel much more intuitive.

Batch matrix multiplication in machine learning#

In real-world machine learning, you rarely multiply just one matrix at a time. Instead, you work with batches of data—multiple matrices processed together. Batch matrix multiplication lets you apply the same operation across many inputs efficiently, which is essential for performance.

What is batch matrix multiplication?#

Batch matrix multiplication means performing matrix multiplication across a stack of matrices at once.

Instead of:

  • One matrix: (m, n) × (n, p) → (m, p)

You now have:

  • A batch of matrices: (batch_size, m, n) × (batch_size, n, p) → (batch_size, m, p)

Key idea#

NumPy treats higher-dimensional arrays (3D or more) as a stack of matrices.

  • Each slice along the first dimension is a matrix

  • Multiplication happens pairwise across the batch

  • The result is another stack of matrices

Think of it like this:

Batch A: [A1, A2, A3]
Batch B: [B1, B2, B3]
Result: [A1 @ B1, A2 @ B2, A3 @ B3]

Example 1: Batch multiplication with np.matmul()#

import numpy as np
# Create a batch of 2 matrices (2×2 each)
A = np.array([
[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]
]) # shape (2, 2, 2)
B = np.array([
[[1, 0],
[0, 1]],
[[2, 0],
[0, 2]]
]) # shape (2, 2, 2)
print("Shape of A:", A.shape)
print("Shape of B:", B.shape)
result = np.matmul(A, B)
print("Result:\n", result)
print("Shape of result:", result.shape)

Output shape: (2, 2, 2)
Each matrix in the batch is multiplied independently.

Example 2: Using the @ operator#

result = A @ B
print("Result using @:\n", result)
print("Shape:", result.shape)

This produces the same result as np.matmul() but is more readable.

Real-world ML use cases#

Batch matrix multiplication shows up everywhere in machine learning:

  • Neural networks: You process multiple inputs (batch_size) at once during training

  • Training pipelines: Data is grouped into batches for efficiency

  • Transformers and attention models: Large batches of matrix operations are computed in parallel

You don’t usually see loops—this batching happens under the hood using operations like @ and matmul.

Important notes#

  • Both np.matmul() and @ support batch multiplication out of the box

  • np.dot() behaves differently with higher dimensions and can give unexpected results

  • Shape alignment rules still apply—you need (batch, m, n) and (batch, n, p)

  • NumPy can also broadcast batches in some cases (for example, (1, m, n) with (batch, n, p))

Common mistake:
If dimensions don’t align (for example, mismatched n), you’ll get a shape error. Always check your shapes before multiplying.

Why this matters#

Batch matrix multiplication is what makes modern ML systems efficient:

  • It enables parallel computation across many inputs

  • It improves performance on large datasets

  • It’s the standard approach used in frameworks like TensorFlow and PyTorch

Once you understand batching, you’re much closer to thinking like a real ML system.

Performance comparison: np.dot() vs np.matmul() vs @ operator#

When you’re choosing between np.dot(), np.matmul(), and the @ operator, it’s natural to wonder if one is faster than the others. In practice, performance differences are usually small because all three rely on highly optimized linear algebra libraries under the hood. Still, it’s useful to benchmark them so you know what actually matters.

Benchmark setup#

The following example benchmarks matrix multiplication across three sizes:

  • Small: 10 × 10

  • Medium: 100 × 100

  • Large: 1000 × 1000

We run each method multiple times and calculate the average execution time.

Code example#

import numpy as np
import time
def benchmark(func, A, B, iterations):
start = time.perf_counter()
for _ in range(iterations):
func(A, B)
end = time.perf_counter()
return (end - start) / iterations
# Define matrix sizes
sizes = [
("10x10", 10, 1000),
("100x100", 100, 100),
("1000x1000", 1000, 10)
]
for label, size, iterations in sizes:
print(f"\nMatrix size: {label}")
# Generate random matrices
A = np.random.rand(size, size)
B = np.random.rand(size, size)
# Benchmark each method
dot_time = benchmark(np.dot, A, B, iterations)
matmul_time = benchmark(np.matmul, A, B, iterations)
at_time = benchmark(lambda x, y: x @ y, A, B, iterations)
print(f"np.dot(): {dot_time:.6f} seconds")
print(f"np.matmul(): {matmul_time:.6f} seconds")
print(f"@ operator: {at_time:.6f} seconds")

Results (example output)#

Matrix size

np.dot() time

np.matmul() time

@ operator time

10×10

0.000010 s

0.000011 s

0.000011 s

100×100

0.0012 s

0.0013 s

0.0013 s

1000×1000

0.120 s

0.121 s

0.121 s

Key observations#

  • Performance differences are minimal in most real-world cases.

  • All three methods rely on the same optimized linear algebra libraries (like BLAS).

  • Small matrices may show tiny overhead differences, but they’re usually negligible.

  • np.matmul() and the @ operator behave identically in performance.

  • For large matrices, computation time dominates, so method choice doesn’t matter much.

What should you use in practice?#

  • Use the @ operator for clean, readable code—it’s the most Pythonic option.

  • Use np.matmul() when you want explicit matrix multiplication, especially for higher-dimensional arrays.

  • Don’t choose between these methods based on performance alone—clarity matters more.

Note: Benchmark results can vary depending on your hardware, NumPy version, and underlying libraries, so your exact numbers may differ.

Wrapping up and next steps#

Congrats on taking your first steps with NumPy matrix multiplication! It’s a complicated yet important part of linear algebra. It helps us further our understanding of different aspects of data science, machine learning, deep learning, and other prevalent fields. There’s still a lot more to learn about NumPy and matrices.

Some recommended topics to cover next are:

  • NumPy matrix transpose

  • NumPy arrays (ndarray)

  • NumPy vectorization

  • numpy.ndarray and ndarray objects

  • Multi-dimensional arrays

Want to get started with Python? Check out Educative’s learning path Become a Python Developer. This hands-on learning path will help you master the skills needed to extract insights from data using a powerful assortment of popular Python libraries.

Happy learning!


Continue learning about Python and NumPy#

Frequently Asked Questions

What is the matrix multiplication symbol in NumPy?

In the context of an array, the symbol * signifies multiplication on an element-by-element basis, whereas @ denotes matrix multiplication. The corresponding functions for these operations are multiply() and dot(), respectively.

Which matrix multiplication algorithm does NumPy use?

The numpy.dot() function calculates the dot product of two NumPy arrays. For one-dimensional and two-dimensional arrays, the result is equivalent to the matmul() function.


Written By:
Erin Schaffer