Dot Product
Multiply two lists of numbers position by position and add up the result — a single number saying how much the two point the same way.
Why Does This Exist?
Every time a search over embeddings returns a result, a dot product decided it. Every attention score inside a transformer is a dot product. Every neuron in a dense layer computes one before it does anything else. It is, in terms of raw arithmetic performed on the planet's GPUs, very likely the single most-executed operation in machine learning.
The reason it earns that position is that it answers a question no individual number can: do these two things point the same way? Two embeddings of 768 numbers each are impossible to compare by eye. One dot product collapses them to a single score, and that score behaves the way intuition wants — large when the vectors agree, zero when they are unrelated, negative when they oppose.
And it comes with a trap attached, which is the practical reason to read past the definition. Because the dot product mixes direction with magnitude, using it as a similarity score without controlling magnitude produces a retrieval system that quietly ranks by vector length. That bug is common, it is invisible in unit tests, and it is fixed by one line.
Think of It Like This
Pushing a trolley at an angle
You are pushing a supermarket trolley. The trolley only rolls forward, but your push comes in at an angle.
Push straight ahead and all of your effort goes into moving it. Push at forty-five degrees and some of your effort is wasted sideways into the handle. Push directly sideways and the trolley does not move at all — you are working hard and achieving nothing forward.
The dot product is exactly the useful part of your push: how much of your effort lies along the direction that matters. Push harder and it grows. Turn more sideways and it shrinks. Turn a full ninety degrees and it hits zero, which is what "orthogonal" means. Turn further and it goes negative, because you are now pulling the trolley backwards.
How It Actually Works
There are two formulas, they always agree, and each is useful for a different reason.
The computational one — multiply matching positions, add everything up:
In plain words: pair up the components, multiply each pair, total the results. Both vectors must have the same length , and the answer is always a single scalar — shapes and give shape .
The geometric one — length times length times the angle between them:
In plain words: the sizes of the two vectors, scaled down by how much they disagree in direction. The bars are the L2 length from norms, and is the angle between the vectors.
The second formula is where all the meaning lives. Because runs from 1 down to :
- Same direction (, ) — the maximum possible value.
- At right angles (, ) — exactly zero. The vectors are orthogonal and share nothing.
- Opposite (, ) — maximally negative.
The magnitude problem, and cosine similarity
Look hard at that second formula, because it contains the trap. The dot product is — two length terms and one direction term. A long vector pointing roughly the wrong way can easily beat a short vector pointing exactly the right way.
If what you want is direction only, divide the lengths out:
That is cosine similarity, and it lands in regardless of how long either vector is. It is the right default for comparing embeddings, because embedding magnitude is usually an artifact of training rather than a meaning you want to rank by.
There is a shortcut worth knowing, because production systems rely on it. If you normalise every vector to length 1 first — making each a unit vector — then and the plain dot product is the cosine. This is why vector databases store normalised embeddings: it buys cosine semantics at dot-product speed.
Worked example
Take and .
Componentwise: .
Now check it geometrically. The lengths are and . Since lies flat along the horizontal axis, the angle of above it has . So . The two formulas agree, as they must.
The projection is the piece the diagram draws: how far along 's direction reaches, which is . Equivalently . That is 's shadow on .
And the cosine similarity is — a strong match, though not a perfect one.
Show Me the Code
import numpy as np
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float: # Divide out both magnitudes so only the angle survives. return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))
a = np.array([4.0, 3.0]) # shape (2,)b = np.array([5.0, 0.0]) # shape (2,)
print(a @ b) # -> 20.0 the dot productprint(np.linalg.norm(a), np.linalg.norm(b)) # -> 5.0 5.0print(cosine_similarity(a, b)) # -> 0.8print(a @ b / np.linalg.norm(b)) # -> 4.0 projection of a onto b
# Length hijacks the raw dot product: 10*b agrees with a no better, but scores 10x.print(a @ (10 * b), cosine_similarity(a, 10 * b)) # -> 200.0 0.8That final line is the whole lesson. Scaling b by ten multiplies the dot product by ten while the cosine similarity does not budge, because the two vectors still point at exactly the same angle to each other.
Watch Out For
Ranking by dot product on unnormalised embeddings
This is the most common retrieval bug there is, and it does not announce itself.
Embedding magnitudes are not uniform. Longer documents, more frequent tokens, and certain fine-tuning regimes all produce systematically longer vectors. If you rank candidates by raw dot product, those long vectors win more often — not because they are more relevant, but because they are longer. Your search quietly becomes a popularity contest.
What makes it hard to catch: the results are not garbage. They are plausible, related, mostly reasonable, and just noticeably worse than they should be. No test fails. The usual symptom is a handful of documents that keep surfacing for unrelated queries.
The fix is one line at write time: normalise every vector to length 1 before you store it, then the dot product is cosine similarity and you keep the speed. Do it at ingestion, not at query time, and confirm it — np.linalg.norm(v) should be 1.0 for everything in the index.
Accumulating a long dot product in low precision
A dot product over 4,096 dimensions is 4,096 multiplications summed into one accumulator, and in reduced precision that accumulator is the weak point.
float16 maxes out around 65,504. Attention scores over a large hidden dimension can exceed that before any softmax gets a chance to normalise them, and the sum becomes inf. From there the softmax produces NaN, and one NaN contaminates every parameter it reaches on the backward pass. This is a genuine failure mode of naive mixed-precision attention, not a hypothetical.
Even short of overflow, precision degrades: adding 4,096 small float16 numbers loses low-order bits at every step, and the error accumulates with the count. This is why the transformer's attention divides scores by before the softmax, and why kernels accumulate in float32 even when their inputs are float16. If you hand-roll an attention or similarity kernel, accumulate wider than you store.
The Quick Version
- Multiply matching components and sum: two vectors of length give one scalar.
- Equivalently — two magnitude terms and one direction term.
- Positive means broadly aligned, zero means orthogonal, negative means opposed.
- Because magnitude is baked in, raw dot products rank partly by vector length.
- Divide by both lengths to get cosine similarity, which measures direction alone and lands in .
- Normalise vectors once at storage time and the dot product becomes cosine for free. This is what vector databases do.
- The projection of onto is .
- Accumulate long dot products in wider precision than you store them in.
What to Read Next
- Norms define the lengths this page divides by, and explain why L2 is the one used here.
- Matrix Multiplication is this operation done many times at once — every entry of the result is one dot product.
- Vectors, Matrices and Tensors covers the shape rules that decide whether two things can even be dotted.
- Definitions worth a look: Cosine Similarity, Unit Vector, Orthogonal Vectors, and Euclidean Distance.
- Related interview question: Why normalise vectors before computing cosine similarity?