Similarity Metrics: Cosine Similarity, Dot Product and Euclidean Distance

~13 min read

Three ways to measure how 'close' two vectors are — cosine similarity (angle), dot product (angle + magnitude), and Euclidean distance (straight-line distance) — each suited to different situations.

The previous subtopic established that vector search finds documents whose embeddings are 'close' to a query's embedding — but 'close' needs a precise mathematical definition, and there are three you'll encounter constantly, each measuring a subtly different notion of closeness.

Cosine similarity, introduced in the previous unit, measures the ANGLE between two vectors, completely ignoring their length/magnitude. Two vectors pointing in the exact same direction score 1.0, regardless of whether one is twice as long as the other. This makes it robust to differences in text length or embedding 'confidence' that show up as vector magnitude rather than meaningful direction — which is exactly why it's the most common default for comparing text embeddings.

Dot product multiplies corresponding elements of two vectors and sums the results — mathematically, it's cosine similarity MULTIPLIED by both vectors' magnitudes (dot product = |a| x |b| x cosine_similarity). Unlike cosine similarity, it DOES care about magnitude — a longer vector pointing in a similar direction produces a bigger dot product than a shorter one pointing the same way. It's computationally slightly cheaper than cosine similarity (no need to divide by the magnitudes), which matters at large scale, and some embedding models are specifically trained so that magnitude carries useful information (e.g. correlating with how confident or salient a piece of text is) — for those models, dot product is the intended metric rather than cosine similarity.

Euclidean distance is the ordinary straight-line distance you'd measure with a ruler if you could see the vector space — literally the same formula as distance between two points on a map, just extended to however many dimensions the embedding has. Unlike the previous two, SMALLER means more similar (it's a distance, not a similarity score) — 0 means identical, and it grows unbounded as vectors get further apart. It cares about BOTH direction and magnitude, and is most intuitive when the actual physical/numeric scale of the space carries meaning (common for non-text embeddings, like geographic coordinates or certain image embeddings).

Which to use: cosine similarity is the safe general default for text embeddings (most sentence-transformer and OpenAI models are documented as intended for cosine similarity or dot product specifically — check your model's docs). Once you've retrieved an initial candidate set with ANY of these fast metrics, this course's RAG workflow adds a further refinement step: re-ranking with a cross-encoder, a heavier model that looks at the query and a candidate document TOGETHER (rather than comparing pre-computed embeddings separately) to produce a more accurate — but much slower — relevance score, only worth affording on the smaller shortlist that fast vector similarity already narrowed down.

💻 Code example

import math

def dot_product(a, b):
    return sum(ai * bi for ai, bi in zip(a, b))

def magnitude(v):
    return math.sqrt(sum(x ** 2 for x in v))

def cosine_similarity(a, b):
    """Angle only -- ignores vector length. Ranges -1 to 1."""
    return dot_product(a, b) / (magnitude(a) * magnitude(b))

def euclidean_distance(a, b):
    """Straight-line distance. SMALLER = more similar (it's a distance)."""
    return math.sqrt(sum((ai - bi) ** 2 for ai, bi in zip(a, b)))

# Two vectors pointing the SAME direction but different lengths
v1 = [1.0, 2.0]
v2 = [2.0, 4.0]   # same direction as v1, exactly 2x longer
v3 = [1.0, 2.1]   # nearly same direction, nearly same length as v1

print(f"cosine(v1, v2)    = {cosine_similarity(v1, v2):.4f}  (same direction -> ~1.0)")
print(f"dot_product(v1,v2) = {dot_product(v1, v2):.4f}  (cares about length too)")
print(f"euclidean(v1, v2) = {euclidean_distance(v1, v2):.4f}  (far apart despite same direction)")
print()
print(f"cosine(v1, v3)    = {cosine_similarity(v1, v3):.4f}")
print(f"euclidean(v1, v3) = {euclidean_distance(v1, v3):.4f}  (small -- v3 is close to v1)")

💬 Deep Dive with AI

Key points

  • Cosine similarity measures only the ANGLE between vectors (ignores length) — the common default for text embeddings, ranging -1 to 1
  • Dot product = cosine similarity x both magnitudes — it DOES factor in vector length, is slightly cheaper to compute, and is the intended metric for some models
  • Euclidean distance is ordinary straight-line distance — unlike the other two, SMALLER means more similar, and it cares about both direction and magnitude
  • Cosine similarity is the safe general default for text; check your specific embedding model's docs, since some are trained for dot product instead
  • Fast similarity metrics narrow down candidates; a heavier cross-encoder re-ranking step (from the book's RAG workflow) can then refine that shortlist more accurately but more slowly