Maths

Cosine similarity vs Euclidean distance: choose what closeness means

Calculate both measures on the same vectors, explain their different rankings, and connect normalized distance to cosine similarity.

Euclidean distance measures the straight-line separation between vectors. Cosine similarity compares their directions by dividing their dot product by their lengths. A long vector can therefore point in exactly the same direction as a query while remaining far away in Euclidean distance.

Neither measure defines the right notion of similarity for every problem. You must decide whether magnitude contains useful information, whether coordinates have comparable scales, and what the vector representation was designed to express. We will calculate both measures on the same invented two-dimensional example and explain why their rankings disagree.

Calculate a case where the rankings differ

Let the query be (1, 1). Candidate A is (10, 10) and candidate B is (1, 0). A points in the query's direction but is much longer. B is nearby but points in a different direction.

For vectors x and y, Euclidean distance is the square root of the sum of squared coordinate differences. Cosine similarity is (x · y) / (||x|| ||y||). This standard-library script implements both and checks the normalized relationship.

from math import sqrt, isclose

def norm(x):
    return sqrt(sum(value * value for value in x))

def distance(x, y):
    if len(x) != len(y):
        raise ValueError("dimensions must match")
    return norm([a - b for a, b in zip(x, y)])

def cosine(x, y):
    if len(x) != len(y):
        raise ValueError("dimensions must match")
    denominator = norm(x) * norm(y)
    if denominator == 0:
        raise ValueError("cosine needs nonzero vectors")
    return sum(a * b for a, b in zip(x, y)) / denominator

query = [1.0, 1.0]
for name, candidate in [("A", [10.0, 10.0]), ("B", [1.0, 0.0])]:
    similarity = cosine(query, candidate)
    print(name, f"distance={distance(query, candidate):.3f}",
          f"cosine={similarity:.3f}")
    q_unit = [value / norm(query) for value in query]
    c_unit = [value / norm(candidate) for value in candidate]
    assert isclose(distance(q_unit, c_unit) ** 2,
                   2 - 2 * similarity, abs_tol=1e-12)

Expected output:

A distance=12.728 cosine=1.000
B distance=1.000 cosine=0.707

Euclidean nearest-neighbor selection chooses B. Highest-cosine selection chooses A. The disagreement is a consequence of the question each measure asks.

Original vectors show A farther from the query than B; after unit normalization, A and the query coincide while B points along the horizontal axis.

Understand the role of normalization

If u and v both have length one, expand the squared distance:

||u - v||² = ||u||² + ||v||² - 2(u · v) = 2 - 2 cos(u, v).

On unit-normalized, nonzero vectors, minimizing Euclidean distance therefore gives the same ordering as maximizing cosine similarity, apart from numerical ties. This is a statement about those normalized vectors. It does not say the original unnormalized distances have the same ordering.

The transformation discards magnitude. That is appropriate only if the task can afford to discard it. Two count vectors with the same proportions but different totals become indistinguishable in direction. If total activity is relevant, consider representing it separately or using a different task-appropriate measure.

Similarity and distance use different conventions

Higher cosine similarity means more aligned directions. Lower Euclidean distance means less separation. Some libraries expose cosine distance, usually 1 - cosine_similarity, so lower values are better there. Read the API before sorting a result table. SciPy's cosine distance reference explicitly uses that convention.

Cosine similarity can be negative when coordinates permit opposite directions. The zero vector has no direction, so the mathematical formula is undefined there. Our implementation rejects it; other APIs may apply their own convention. Handle empty or degenerate representations deliberately rather than letting a library default define the application's meaning of similarity.

Check the representation before choosing a measure

Euclidean distance is sensitive to units: a coordinate measured in thousands can dominate one measured between zero and one. Normalizing vector lengths does not automatically solve all coordinate-scaling problems. Likewise, cosine similarity cannot repair an embedding that fails to represent the distinctions the user cares about.

In retrieval, follow the representation model's documented training and comparison assumptions, then evaluate on relevant query-document judgments. Do not infer answer quality from geometric proximity alone. The RAG retrieval evaluation example shows how to test retrieval against explicit expected evidence.

Test another geometry

Replace A with (-1, -1), calculate both measures by hand and verify the result. Then multiply B by a positive constant. Which quantity changes, and which stays the same? Finally, normalize all three vectors and compare their orderings again.

Use the Maths resources to connect vector calculations to numerical methods and the course syllabus. The useful habit is to explain what information a transformation preserves before using it to make decisions.

Related course

The maths machine learning uses: functions and logarithms, reading ML notation, vectors and the dot product, matrices, derivatives and gradients, each shown on a picture before the formula.

Open lessons' parts and key terms are free to read, with no account.

What you will be able to do

  • Follow the maths behind a model's training loop.
  • Find a derivative and a gradient, and say what it means.
  • Use vectors, dot products and matrices to describe data.
  • Read machine learning notation aloud in words.