← Back to RAG and Vector Databases
Lesson 2.6 · Embeddings and Semantic Search

Cosine Similarity, Dot Product, and Euclidean Distance

Three ways to measure "close", written in plain Python and compared on the same vectors. For length-1 vectors they agree on every ranking - and Euclidean² = 2 − 2·cosine exactly (0.5075 both). Make one vector three times longer and they disagree: dot product puts the wrong document first, Euclidean puts it last, and only cosine stays right.

Intermediate25 min

What you will be able to do

  • Write cosine, dot product and Euclidean distance in Python
  • Read each score: higher or lower is better
  • See that all three agree for normalised vectors
  • See how vector length changes dot product and Euclidean
  • Choose a measure for your vector database

The idea, in plain English

Search needs one number for "how close are these two vectors?". There are three common ways. Cosine similarity measures only the angle between them. Dot product multiplies them number by number and adds up - it grows with the angle AND the lengths. Euclidean distance measures the straight line between the two points - here, smaller means closer.

Our vectors have length 1 (Lesson 2.5). Then the three are tied together: cosine and dot product are the same number, and Euclidean distance follows from it. They always produce the same ranking - which is why Lesson 2.1 could use a simple sum.

When vectors are NOT length 1 - some models and some stored data - the three can disagree. We show both cases with real vectors, in plain Python with no libraries.

Worked example: One question and three bakery sentences, scored three ways - then the same with one vector made three times longer.

workflowThree measures, one questionstep 1 / 2

1 - Length-1 vectors

All three give the same order: refunds, hours, delivery.

refunds
cos 0.746 = dot 0.746, dist 0.712
check
dist² 0.5075 = 2 - 2·cos 0.5075

"Can I return my cake?" against three sentences. Real scores.

Words you will see in this lesson

A few measuring words.

Small dictionary
Cosine similarityHow much two vectors point the same way: 1 same, 0 unrelated, -1 opposite.
Dot productMultiply number by number and add everything up.
Euclidean distanceThe straight-line distance between two points. Smaller = closer.
Similarity vs distanceFor similarity, higher is better; for distance, lower is better.
MetricThe measure a vector database uses to compare vectors.

An everyday example: two people walking

  • Two friends leave the same gate. Cosine asks: are they walking in the same DIRECTION? It does not care how far each has walked.
  • Euclidean asks: how far apart are they NOW? Same direction, but one walked three times farther - now they are far apart.
  • Dot product mixes both: same direction AND long walks give a big number.
  • If everyone walks exactly one kilometre (length 1), the three questions give the same answer.

Example 1 - the three measures in Python

metrics.py writes each measure in a line or two, then compares them:

  • dot: multiply and add. length: the square root of a vector’s dot product with itself.
  • cosine: dot divided by both lengths - so length cancels out.
  • euclidean: the square root of the summed squared differences.
  • report() prints all three scores and the ranking each one gives. For Euclidean, smaller is better, so its ranking is sorted the other way.
Example 1 - metrics.py
import math import ollama 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)) # the angle only; length does not matter def euclidean(a, b): return math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b))) # straight-line distance def report(title, q, docs): print(title) print(f" {'document':<12}{'cosine':>9}{'dot':>9}{'euclidean':>11}") for name, d in docs.items(): print(f" {name:<12}{cosine(q, d):>9.3f}{dot(q, d):>9.3f}{euclidean(q, d):>11.3f}") for label, key, best_high in [("cosine", cosine, True), ("dot", dot, True), ("euclidean", euclidean, False)]: order = sorted(docs, key=lambda n: key(q, docs[n]), reverse=best_high) print(f" ranking by {label:<9}: {order}") texts = {"refunds": "Custom cakes can be refunded if cancelled 24 hours before pickup.", "delivery": "We deliver within 8 km of MG Road; orders above Rs 1000 are free.", "hours": "Open Monday to Saturday 8 am to 9 pm, Sunday 9 am to 2 pm."} vectors = ollama.embed(model="nomic-embed-text", input=list(texts.values())).embeddings docs = dict(zip(texts, vectors)) q = ollama.embed(model="nomic-embed-text", input="Can I return my cake?").embeddings[0] report("1. Vectors of length 1 (what Ollama returns)", q, docs) print(f" check: euclidean^2 = {euclidean(q, docs['refunds']) ** 2:.4f}, 2 - 2*cosine = {2 - 2 * cosine(q, docs['refunds']):.4f}") docs["hours"] = [x * 3 for x in docs["hours"]] # the same direction, three times as long print() report("2. The 'hours' vector made 3 times longer", q, docs)
Output
1. Vectors of length 1 (what Ollama returns) document cosine dot euclidean refunds 0.746 0.746 0.712 delivery 0.339 0.339 1.150 hours 0.357 0.357 1.134 ranking by cosine : ['refunds', 'hours', 'delivery'] ranking by dot : ['refunds', 'hours', 'delivery'] ranking by euclidean: ['refunds', 'hours', 'delivery'] check: euclidean^2 = 0.5075, 2 - 2*cosine = 0.5075 2. The 'hours' vector made 3 times longer document cosine dot euclidean refunds 0.746 0.746 0.712 delivery 0.339 0.339 1.150 hours 0.357 1.070 2.803 ranking by cosine : ['refunds', 'hours', 'delivery'] ranking by dot : ['hours', 'refunds', 'delivery'] ranking by euclidean: ['refunds', 'delivery', 'hours']

Reading part 1 - vectors of length 1

  • Cosine and dot are the same numbers: 0.746, 0.339, 0.357. With length 1, dividing by the lengths changes nothing.
  • Euclidean is lowest where cosine is highest: 0.712 for refunds.
  • The check line proves the link: Euclidean² = 0.5075 and 2 − 2·cosine = 0.5075. One follows from the other.
  • So all three rankings are refunds, hours, delivery. For normalised vectors, use dot product - it is the cheapest to compute.

Reading part 2 - one vector three times longer

We multiplied every number of the "hours" vector by 3. Its direction did not change - only its length:

  • Cosine: exactly the same as before (0.357). It only sees direction.
  • Dot product: 0.357 became 1.070 - three times bigger - and "hours" jumped to FIRST, above refunds. Wrong.
  • Euclidean: the distance grew to 2.803, and "hours" fell to LAST. Also changed, the other way.
  • Real vectors are not usually scaled on purpose. But some models return vectors that are not length 1, and some stored data is not normalised - then the measure you choose changes your results.

Which one to choose

  • Your model returns normalised vectors (like here): any of the three gives the same ranking. Dot product is fastest.
  • Not normalised: use cosine - or normalise the vectors yourself once when you store them, then use dot product.
  • Use the measure the model was trained with; its documentation says which.
  • Vector databases ask you to pick the metric when you create a collection (Modules 3 and 4). Pick it on purpose - changing it later means rebuilding the index.

Similarity measures at a glance

Dot product

Higher = closer.

sum(x * y for x, y in zip(a, b))
Cosine

Higher = closer; ignores length.

dot(a, b) / (length(a) * length(b))
Euclidean

Lower = closer.

math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)))
Length-1 link

All agree.

euclidean² = 2 - 2 * cosine

Try it yourself

The code does not change. Swap the content string and the program does something else entirely.

Normalise it back

“After making "hours" 3 times longer, divide it by its length. Do the rankings agree again?”

Opposite direction

“Multiply a vector by -1. What happens to the cosine score?”

Speed

“Time 10,000 dot products and 10,000 cosines in plain Python. Which is faster?”

Your sentences

“Replace the three sentences with three of your own and check the length-1 link still holds.”

What usually goes wrong

Sorting a distance the wrong way

For Euclidean, smaller is closer. Sorting high-first gives the worst match.

Dot product on vectors that are not length 1

A longer vector jumped from second to first place.

Changing the metric later

A vector database builds its index for one metric. Choose it when you create the collection.

Practice

Write these yourself before opening anything. Getting them wrong first is most of how this sticks.

1.

Write normalise(v) and a function search(question, docs, metric) that supports "cosine", "dot" and "euclidean". Load vectors.json from Lesson 2.4, deliberately scale two of the stored vectors by 2 and 0.5, and show which metrics still return the right document for three questions. Then normalise all stored vectors and show that all three metrics agree again.

Show hint

Remember that for "euclidean" the best match is the smallest value - use min() instead of max().

Key points

  • Cosine measures the angle; dot product measures angle and length; Euclidean measures distance.
  • For similarity higher is better; for distance lower is better.
  • With length-1 vectors all three give the same ranking - and Euclidean² = 2 − 2·cosine.
  • With other lengths, dot product and Euclidean change; cosine does not.
  • Normalise once and use dot product, or use cosine.

Quick check before you move on

What were cosine and dot product for refunds with length-1 vectors?
Both 0.746.
After making "hours" 3x longer, which measure ranked it first?
Dot product (1.070).
Which measure did not change at all?
Cosine.

Interview questions

When do cosine similarity and dot product give different results?

When vectors are not normalised: dot product grows with vector magnitude, so longer vectors are favoured, while cosine depends only on the angle.

How is Euclidean distance related to cosine for unit vectors?

For unit vectors, squared Euclidean distance equals 2 − 2·cosine, so both produce the same nearest-neighbour ranking.

Quiz

  1. 1.

    Why are cosine and dot product equal here?

  2. 2.

    For Euclidean distance, is 0.712 or 1.150 the better match?

  3. 3.

    Why is dot product a good choice for normalised vectors?

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...