Topic 2.2
Dot Product, Cosine and Distance
In one line
Three ways to compare two vectors: multiply and add (dot product), compare the direction only (cosine), or measure the gap between the points (distance). They rank differently when vectors have different lengths and identically when every vector has length 1.
Think of it like this
Two people pointing at the sky. Cosine asks 'are you pointing the same way?', and ignores how long your arm is. Distance asks 'how far apart are your fingertips?', and your arm length matters. The dot product mixes both: it grows when you point the same way *and* when your arms are long. Which question you ask changes who counts as 'closest'.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Dot product
- Multiply two vectors slot by slot and add up. Big when they point the same way, and big when they are long.
- Cosine similarity
- The dot product divided by both lengths. It only looks at direction: 1 same, 0 unrelated, -1 opposite.
- Euclidean distance
- The straight-line gap between two points. Smaller means more similar.
- Length (norm)
- How long a vector is: the square root of the sum of its squares.
- Unit vector
- A vector of length 1.
- Similarity versus distance
- Similarity grows as things get closer, distance shrinks. Rank accordingly: highest similarity first, lowest distance first.
Step by step
01Step 1: the dot product by hand
For query [3, 4] and candidate A [6, 8]: 3*6 + 4*8 = 18 + 32 = 50. For B [4, 3]: 12 + 12 = 24. For C [-3, -4]: -9 - 16 = -25. A negative dot product means the vectors point in opposite directions.
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def length(a):
return math.sqrt(dot(a, a))
def cosine(a, b):
return dot(a, b) / (length(a) * length(b))02Step 2: cosine removes the length
A is exactly twice as long as the query but points the same way, so its cosine is 1.000. B points slightly differently (cosine 0.960). C points the opposite way (-1.000). Cosine answers only 'same direction?'.
03Step 3: distance asks a different question
The gap from the query to A is 5.000, to B only 1.414, to C 10.000. Distance puts B first. It prefers a vector that is simply *near* in space, even if the direction is slightly different.
04Step 4: make the vectors the same length
Scale every vector to length 1 (Topic 2.3). Now only direction is left. In the second example all three measures return D2, D1, D4, D3.
05Step 5: the identity that ties them together
For unit vectors, distance^2 = 2 - 2 * cosine. The example prints both sides for D1: 0.4000 and 0.4000. So 'highest cosine' and 'smallest distance' are the same ordering, and you can use whichever is fastest in your store.
06Step 6: check what your model expects
Model documentation says which measure it is trained for, and whether its output is already normalised. When it says 'cosine', a store set to dot product is fine only if you normalise the vectors first.
Try it yourself
- 1
Predict the order
Change the candidates to
A: [2, 0],B: [1, 1],C: [0, 3]with the query[1, 0]. Before running, write down the order by dot, by cosine and by distance. Which one disagrees? - 2
Make a long vector win
In the first example, change A to
[60, 80]. Does cosine change? Does the dot product? Does distance? What does that tell you about comparing vectors of different lengths? - 3
Check the identity yourself
In the second example, print the identity for D3 instead of D1. Is
2 - 2*cosinestill equal to the distance squared? What if one vector is not normalised?
Code & diagrams
Two-number vectors so you can check each result by hand. Real vectors have hundreds of numbers, and the maths is the same.
Expected output
query: [3, 4] A: [6, 8] (same direction, longer) B: [4, 3] (close by) C: [-3, -4] (opposite)
A: dot=50.0 cosine=1.000 distance=5.000
B: dot=24.0 cosine=0.960 distance=1.414
C: dot=-25.0 cosine=-1.000 distance=10.000
ranked by dot: A, B, C
ranked by cosine: A, B, C
ranked by distance: B, A, CExpected output
unit vectors, ranked by dot: D2, D1, D4, D3
unit vectors, ranked by cosine: D2, D1, D4, D3
unit vectors, ranked by distance: D2, D1, D4, D3
D1: cosine=0.8000 distance squared=0.4000 2 - 2*cosine=0.4000Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Call it cosine, but return the dot product
In the Python tab, change the body of cosine to return dot(a, b) (forget to divide by the lengths). Run.
Myth vs fact
Myth
Cosine, dot product and distance are interchangeable.
Fact
They rank the same only when all vectors have length 1. With different lengths they can disagree, as the first example shows.
Myth
A cosine of 0 means the texts are opposites.
Fact
0 means unrelated (at right angles). -1 would be opposite, and real text embeddings rarely go far below 0.
Myth
Smaller is always better for a score.
Fact
Only for distance. For dot product and cosine, larger means more similar. Mixing the two up reverses a ranking.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Stores often implement 'cosine' as 'normalise, then dot product', because the dot product is the cheapest operation and hardware does it fast. If the vectors are already unit length, the normalising is wasted work you can skip, but only if you are sure (Topic 2.3).
- ▸
Some stores report cosine distance, which is
1 - cosine: smaller is better, and 0 means identical. Check which one a score is before you set a threshold on it. - ▸
Distance and dot product are sensitive to vector length. Some models are trained with a dot-product objective, so their lengths may be part of what they learned. Do not normalise a model's output unless its documentation says that is safe.
Remember this
- 1
Why this exists. Topic 2.1 ended with 'similar texts get similar vectors'. Search needs a number for 'how similar', so it can sort. There is more than one reasonable number, and the choice matters because the vectors a model produces are not always the same length. A wrong choice quietly gives a wrong ranking.
- 2
Dot product. Multiply the two lists slot by slot and add the results:
[3, 4]and[6, 8]give3*6 + 4*8 = 50. It is large when the vectors point the same way and also large when the vectors are long. It is the fastest of the three, which is why stores like it. - 3
Cosine similarity. Divide the dot product by both lengths:
dot(a, b) / (length(a) * length(b)). The lengths cancel out, so only the angle between the vectors is left. The result runs from 1 (same direction) through 0 (unrelated, at right angles) to -1 (opposite). It is the usual choice for text. - 4
Euclidean distance. The straight-line gap between the two points: subtract slot by slot, square, add, take the square root. Here *smaller* means closer, the reverse of the other two. The example below shows the trap: for the query
[3, 4], the long vector[6, 8](same direction) wins by dot and cosine, but the short vector[4, 3](close by) wins by distance. - 5
When the three agree. If every vector has length 1, the lengths drop out and the three rank the same way. The dot product *is* the cosine, and distance squared is
2 - 2 * cosine, so a larger cosine always means a smaller distance (the second example proves it for D1: cosine 0.8000, distance squared 0.4000, and 2 - 2*0.8 is also 0.4). This is why most embedding models return length-1 vectors and why stores say 'use dot product on normalised vectors' (Topic 2.3). - 6
Which one to use. Use the one the model was trained with. Most text embedding models are trained for cosine similarity, and many output unit vectors, so dot and cosine are the same. Read the model's documentation, and use the same measure for the index as for the query. Never mix measures across one index.
Explain it without notes
What is the difference between the dot product and cosine similarity, and when are they equal?
In the example, why does distance rank B first while cosine ranks A first?
Why do embedding stores usually let you pick a distance measure when you create an index?
A colleague set a similarity threshold of 0.8 and switched the store from cosine to distance, keeping 0.8. What goes wrong?
Practice
Write cosine(a, b) and distance(a, b) and print both for a = [1, 0], b = [0.6, 0.8] to three decimals. Which is larger, and what does distance^2 equal for these unit vectors?
Write rank(query, docs, measure) where docs is a dict of name to vector and measure is "cosine" or "distance". Return names best first. Print the order for the query [1, 0] and docs {"x": [2, 0], "y": [0.5, 0.5]} under both measures.
Trade-offs
- ↔
Cosine: robust to vector length, the natural measure for text; needs two lengths (or normalised input).
- ↔
Dot product: the cheapest, and equal to cosine on unit vectors; misleading when lengths differ.
- ↔
Distance: intuitive for points in space; the direction of 'better' is reversed, and it is length-sensitive.
Done when you can
Done when you can compute dot, cosine and distance for two small vectors by hand.
Done when you can say which of the three ranks differently for vectors of different lengths, and why.
Done when you can state the identity between distance and cosine for unit vectors.
Done when you know where to look up which measure a model expects.