Skip to content
datarekha
NLP & LLMs Easy Asked at GoogleAsked at AmazonAsked at Microsoft

Why is cosine similarity preferred over Euclidean distance for comparing text vectors?

The short answer

Usually, cosine similarity is preferred because it compares vector direction rather than raw length, so documents with different lengths but similar term or embedding patterns can still match. Euclidean distance also reflects magnitude, which is useful only when magnitude is meaningful and the vectors are on a comparable scale.

How to think about it

The direct answer

Usually, cosine similarity is preferred because it compares the direction of two text vectors, not their raw magnitude. Euclidean distance measures the straight-line gap between vectors, so it can treat a long document and a short document about the same subject as dissimilar simply because one contains more words.

That is a useful default, not a law of nature. If vector magnitude carries meaningful information, Euclidean distance or dot product may be the better choice.

Why cosine similarity fits text

A text vector represents a document as numbers. In a bag-of-words or TF-IDF representation, each coordinate usually corresponds to a term in the vocabulary. In an embedding, each coordinate is a learned feature rather than a named word.

The magnitude of a vector is its length. The L2 norm, written as ||a||, is the ordinary geometric length:

||a|| = sqrt(a_1^2 + a_2^2 + ... + a_n^2)

The direction describes the relative pattern of values across coordinates. Two documents can have different magnitudes but the same direction if one is essentially a scaled-up version of the other.

Cosine similarity measures the angle between two non-zero vectors:

cosine_similarity(a, b) = (a · b) / (||a|| * ||b||)

The dot product in the numerator rewards coordinates that are large in both vectors. Dividing by both vector lengths removes the effect of scale. The result is therefore about alignment.

A cosine score of 1 means the vectors point in exactly the same direction. They do not need to contain the same numbers. The vectors [1, 1] and [10, 10] have cosine similarity 1 because one is a scaled copy of the other.

A score of 0 means the vectors are orthogonal: they share no directional signal. A score of -1 means they point in opposite directions. Text vectors made from non-negative word counts or TF-IDF values normally produce scores from 0 to 1; dense embeddings can contain negative coordinates and can produce negative scores.

Euclidean distance behaves differently:

euclidean_distance(a, b) = sqrt(sum_i((a_i - b_i)^2))

It measures both direction and scale. If one document has ten times as many weighted terms as another, Euclidean distance sees that size difference even when the documents have the same term proportions.

That is the central reason cosine is common in text retrieval.

A concrete support-search example

Imagine a support-search system with just two vocabulary coordinates:

  • coordinate one: refund
  • coordinate two: shipping

A short FAQ contains both terms once:

a = [1, 1]

A longer article uses the same terms in the same proportion:

b = [10, 10]

A third document mentions only refund:

c = [1, 0]

Here is the calculation:

import numpy as np

a = np.array([1.0, 1.0])
b = np.array([10.0, 10.0])
c = np.array([1.0, 0.0])

def cosine(x, y):
    return np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))

print(f"cos(a, b) = {cosine(a, b):.3f}")
print(f"euclidean(a, b) = {np.linalg.norm(a - b):.3f}")
print(f"cos(a, c) = {cosine(a, c):.3f}")
print(f"euclidean(a, c) = {np.linalg.norm(a - c):.3f}")

The output is:

cos(a, b) = 1.000
euclidean(a, b) = 12.728
cos(a, c) = 0.707
euclidean(a, c) = 1.000

Euclidean distance says c is closer to a than b is. That is mathematically correct. But if b is a long article with the same vocabulary pattern as the short FAQ, the result is often not what a search system wants.

Cosine says a and b are perfectly aligned, while c is only partially aligned. It ignores the fact that the second article has ten times the raw count because that difference is probably document length rather than topic.

Real TF-IDF vectors are more complicated than this two-coordinate example. Term frequency measures how much a term appears in a document. Inverse document frequency, or IDF, reduces the weight of terms that appear in many documents. The resulting vector is sparse: most vocabulary coordinates are zero.

Cosine still works for the same reason. The numerator focuses on shared weighted terms, while the denominator prevents a document from winning merely because it contains more text.

What changes with embeddings

The same formula applies to dense sentence or document embeddings. These vectors may have hundreds or thousands of learned coordinates, and no single coordinate necessarily means “refund” or “shipping.” The model arranges vectors so that useful relationships are reflected in their geometry.

Cosine similarity is often a sensible metric because many embedding models are trained or evaluated with directional similarity in mind. A query and a relevant passage should point in a similar direction even if one passage has more tokens.

But the embedding model determines what the geometry means. Cosine is not a semantic spell. A TF-IDF model may consider “car” and “automobile” unrelated because they occupy different word coordinates. An embedding model may place them near each other because it learned their relationship from context. Conversely, an embedding model trained on general web text may miss a distinction that matters in medical, legal, or internal company language.

The candidate who says “cosine is always best for embeddings” is overselling it. The correct metric is part of the model’s contract. Check how the embedding was trained, whether vectors are already normalized, and evaluate retrieval on real labeled queries.

The nuance that earns the senior signal

Cosine deliberately throws magnitude away. That is usually helpful for document length, but magnitude can carry signal.

Suppose a model produces two vectors pointing in the same direction, but one has a much larger norm because the model uses norm to express confidence or importance. Cosine gives them the same score. Dot product preserves the norm:

a · b = ||a|| * ||b|| * cosine(a, b)

That makes dot product sensitive to both alignment and vector length. It can be useful when the model was trained for maximum inner-product search and its norms are meaningful. It can also be harmful when high norms are merely an artifact of longer chunks or inconsistent preprocessing.

Cosine can also overvalue tiny pieces of text. A one-sentence passage containing a rare term may point in almost the same direction as a much richer passage, especially in a very sparse representation. A production search system may therefore combine cosine with metadata, lexical matching, quality signals, or a minimum amount of evidence. The right response is not to pretend one similarity score captures relevance perfectly.

Euclidean distance is not automatically wrong. It is reasonable when:

  • all features are on comparable scales;
  • absolute position and magnitude have meaning;
  • the model was trained with Euclidean loss;
  • vectors have already been normalized;
  • the application cares about geometric closeness rather than only orientation.

Normalization changes the comparison. If both vectors are L2-normalized so their lengths equal 1, then:

||a - b||^2 = 2 - 2 * cosine(a, b)

On the unit sphere, smaller Euclidean distance is exactly the same ranking as larger cosine similarity. This is why an approximate-nearest-neighbor system using inner product can implement cosine search: normalize every stored vector and every query vector first.

The word “every” matters. If the index contains raw vectors but the application claims to use cosine, it is not using cosine merely because the code calls the score a similarity.

The production pattern and a common failure

For a TF-IDF matrix or embedding matrix, a typical pattern is:

from sklearn.preprocessing import normalize

X = normalize(X, norm="l2", axis=1)
q = normalize(q, norm="l2", axis=1)

scores = X @ q.T

After normalization, the matrix multiplication is cosine similarity because each row has unit length. In scikit-learn, TfidfVectorizer uses L2 normalization by default, unless you set norm=None or choose another setting. It is still worth checking rather than relying on a hidden default.

A common production failure looks like this: retrieval quality drops after the team changes chunk size, and the first symptom is that long passages or unusually high-norm embeddings appear near the top even when their wording is only loosely related. The underlying bug is often a metric mismatch: raw vectors were inserted into an inner-product index, or the vectors were normalized during one pipeline stage but not another.

The fix is to verify the whole path:

  1. Confirm that the document and query use the same embedding model and preprocessing.
  2. Measure vector norms before indexing and at query time.
  3. Normalize both sides when the intended metric is cosine.
  4. Confirm that the search index uses a compatible metric.
  5. Test known pairs, including identical vectors, scaled copies, unrelated vectors, and zero vectors.

Zero vectors deserve special attention. An empty document, an out-of-vocabulary query, or an aggressively filtered sentence can produce a vector whose norm is zero. Cosine then divides by zero and produces an undefined value or NaN. Handle that case explicitly instead of allowing one malformed query to contaminate ranking or monitoring.

What they’ll ask next

“Is cosine similarity always between zero and one?”

No. For general real-valued vectors, it ranges from -1 to 1. TF-IDF and raw count vectors are non-negative, so their scores normally fall between 0 and 1. Dense embeddings can contain negative values, so negative cosine scores are possible.

“If the vectors are normalized, can I use Euclidean distance?”

Yes. For unit-length vectors, squared Euclidean distance is 2 - 2 * cosine. The two metrics produce the same nearest-neighbor ranking. They produce different-looking score values, so thresholds must still be converted or recalibrated.

“When would you choose dot product instead?”

Choose dot product when vector magnitude is intentionally meaningful or when the model was trained for maximum inner-product retrieval. Otherwise, norm can become an accidental popularity or length signal. For ordinary text comparison, cosine is safer because it removes that uncontrolled scale effect.

Say this in the interview

“Cosine similarity is usually preferred for text because it compares the direction of sparse or embedding vectors, which captures their term or semantic pattern while ignoring document length; Euclidean distance also reflects magnitude, so I would use it only when vector scale is meaningful or after confirming that normalization makes the two metrics equivalent.”

Keep practising

All NLP & LLMs questions

Explore further