What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
For NumPy arrays, multiply matrices with A @ B (the clearest choice), np.matmul(A, B) (the explicit function form), or np.dot(A, B) (equivalent for two-dimensional inputs). Do not use A * B for a matrix product: NumPy reserves * for element-by-element multiplication.
If A has shape (m, n), B must have shape (n, p), and the result has shape (m, p). The important difference appears with vectors and arrays above two dimensions, where matmul broadcasts stacks of matrices while dot follows a different axis-contraction rule.
Set up a small, reproducible example
Install NumPy in the environment that will run your script:
python -m pip install numpy
Then create two compatible two-dimensional arrays:
import numpy as np
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
Both arrays have shape (2, 2), so their product is defined. Every method below returns:
#1 Best Overall
[[19 22]
[43 50]]
That result comes from row-by-column products: the top-left value is 1*5 + 2*7, not an elementwise pairing of 1 with 5 alone.
1. Use Python’s @ operator
The shortest and most readable NumPy spelling is:
C = A @ B
print(C)
print(C.shape) # (2, 2)
PEP 465 introduced @ and @= in Python 3.5. Python defines the operator protocol; array libraries decide what the operation means for their own types. NumPy ndarrays implement @ with matmul semantics.
When @ is the best choice
- Use it in ordinary numerical code where the reader can see that both operands are arrays.
- Use it for chained products such as
prediction = X @ weights + bias. - Use
@=when you intentionally want the left-hand variable replaced by its matrix product, for exampleA @= B. Check shapes first, because the assignment still has to fit the resulting array.
Shape validation
For two-dimensional arrays, the inner dimensions must match. This succeeds:
A = np.ones((3, 4))
B = np.ones((4, 2))
C = A @ B # shape (3, 2)
This fails with a dimension-mismatch error because 3 and 5 do not match:
Rank #2
A = np.ones((2, 3))
B = np.ones((5, 4))
C = A @ B
2. Call np.matmul explicitly
np.matmul(A, B) performs the same operation as A @ B for NumPy arrays:
C = np.matmul(A, B)
print(C)
Why choose the function form?
- It makes the operation obvious in code that is teaching array shapes or accepting an operation as a callable.
- It exposes function arguments such as
out=when you need NumPy to write into a preallocated output array. - It is the natural form when composing NumPy functions or reading code that already uses function calls consistently.
NumPy’s documented recommendation for a two-dimensional matrix product is @ or matmul. They share the same matrix and batch rules, so select between them for readability rather than numerical meaning.
Stacks of matrices and broadcasting
For arrays with three or more dimensions, matmul treats the final two axes as each matrix and broadcasts the earlier axes as batch dimensions. For example:
A = np.ones((7, 2, 3))
B = np.ones((1, 3, 4))
C = np.matmul(A, B)
print(C.shape) # (7, 2, 4)
Each of the seven 2x3 matrices in A is multiplied by the compatible 3x4 matrix in B; the leading dimension of B broadcasts from 1 to 7. If batch dimensions cannot broadcast, NumPy raises an error rather than silently reshaping your data.
Vectors are a special case
matmul handles one-dimensional operands by temporarily treating them as a row or column for the calculation and then removing that temporary axis. Thus a shape (3,) vector multiplied by a shape (3, 2) matrix produces shape (2,), while a shape (2, 3) matrix multiplied by a shape (3,) vector produces shape (2,). A zero-dimensional scalar is not a valid operand for matmul; use ordinary scalar multiplication for that case.
3. Use np.dot
For two-dimensional arrays, this is also a matrix product:
C = np.dot(A, B)
print(C)
Consequently, existing code that uses dot with two-dimensional arrays is mathematically correct. NumPy documentation nevertheless favors @ or matmul when you specifically mean matrix multiplication, because dot changes its contraction behavior as dimensionality increases.
Free tools Windows power users keep installed
One-click scans. No signup required.
The higher-dimensional difference
For inputs above two dimensions, dot contracts the last axis of its first argument with the second-to-last axis of its second argument. It then keeps the remaining axes in a result whose layout follows that rule. For example:
a = np.ones((2, 3, 4))
b = np.ones((5, 4, 6))
c = np.dot(a, b)
print(c.shape) # (2, 3, 5, 6)
That is not the same interpretation as a stack of matrices. matmul would regard the last two axes of each operand as the matrix dimensions and try to broadcast the leading batch dimensions. In this example, batch dimensions (2,) and (5,) cannot broadcast, so a @ b raises a shape error instead of returning (2, 3, 5, 6).
For batched linear algebra, choose @ or matmul and make the batch axes explicit. Reserve dot for code whose contraction rule is intentional, or for straightforward two-dimensional products where you are maintaining an existing API.
Do not confuse * with a matrix product
NumPy’s multiplication operator is elementwise:
A * B
# array([[ 5, 12],
# [21, 32]])
Each position is multiplied independently. Elementwise multiplication can broadcast compatible shapes, but it never performs row-by-column summation. Use * for masks, per-feature scaling, and other corresponding-element operations; use @, matmul, or (in the two-dimensional case) dot for matrix algebra.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Comparison at a glance
| Expression | 2-D inputs | Higher-dimensional inputs | Best use |
|---|---|---|---|
A @ B |
Matrix product | Broadcasted stack-of-matrices semantics | Concise everyday NumPy code |
np.matmul(A, B) |
Matrix product | Same semantics as @ |
Explicit calls and shape-focused code |
np.dot(A, B) |
Matrix product | Contracts last axis of A with second-to-last axis of B |
Two-dimensional legacy code or deliberate dot contraction |
A * B |
Elementwise multiplication | Elementwise broadcasting | Corresponding entries, not matrix algebra |
Debugging matrix-multiplication errors
“Mismatch in core dimension” or incompatible dimensions
- Print both shapes immediately:
print(A.shape, B.shape). - For 2-D operands, verify
A.shape[1] == B.shape[0]. - For batched operands, verify the final two axes are
(m, n)and(n, p), then check that all leading batch axes broadcast. - Do not “fix” an error with a blind transpose. Transpose only when your data’s mathematical orientation is actually reversed.
Unexpected four-dimensional output from dot
This is usually the documented high-dimensional dot contraction, not corruption. Replace it with @ or np.matmul when your arrays represent batches of matrices, and arrange the batch and matrix axes accordingly.
Best Value
Unexpected numbers
- Check whether you wrote
*when you intended@. - Inspect dtypes with
A.dtypeandB.dtype. Integer arrays remain integer arrays, so very large integer products can overflow the selected integer type. - Verify that rows and columns represent the dimensions you think they do; a valid product can still be mathematically mis-oriented.
Python lists do not behave like ndarrays
Convert nested lists before multiplying:
A = np.asarray([[1, 2], [3, 4]])
B = np.asarray([[5, 6], [7, 8]])
C = A @ B
Keeping the conversion at the input boundary gives you predictable shape checks and NumPy’s matrix operators throughout the rest of the program.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance, memory, and reproducibility
- Choose the operator by semantics first; the three forms are interfaces to the same NumPy matrix-multiplication behavior for the cases described above, not separate mathematical algorithms.
- Large products allocate a result array. If you repeatedly multiply arrays in a loop, plan memory use and consider the
out=argument ofnp.matmulwhen its documented constraints fit your code. - Keep shapes explicit at API boundaries and assert them in tests. A small check such as
assert A.shape[-1] == B.shape[-2]catches many data-pipeline mistakes before an expensive product. - Record NumPy and Python versions for reproducible numerical work. Exact floating-point results can vary slightly across platforms and numerical backends even when the operation and inputs are the same.
Or skip the browser setup
If what you actually need is a clean image or PDF of a documentation page, ScreenshotNeo provides a single HTTP request instead of a locally managed browser. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms along with newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
For example, this cURL request captures the NumPy site as WebP (see the ScreenshotNeo API documentation for all options):
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://numpy.org -o shot.webp
The equivalent Python call is:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://numpy.org"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
From Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://numpy.org' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo includes full-page lazy-image loading, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, click and wait actions, request/resource blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify migration.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing provides two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to start without a card.
FAQ
Can I multiply matrices without NumPy?
Yes, you can write nested Python loops, but you must implement dimension checks, accumulation, and type handling yourself. NumPy is the practical choice for array-based numerical programs.
Should new two-dimensional code use dot?
It works, but current NumPy guidance favors @ or matmul because their names and behavior communicate matrix multiplication more directly, especially when code later grows to batched arrays.
Recommended Free Tools
How do I check a product’s dimensions before calculating it?
Inspect A.ndim and A.shape, then validate the inner matrix axes and any batch-axis broadcasting. Printing shapes in a failing test is often more useful than printing full array contents.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

