Cosine vs Dot Product vs Euclidean: Which Similarity Metric Should You Use?
On normalized embeddings all three metrics rank identically, so pick dot product for speed; magnitude only matters with unnormalized vectors.
Contents
Short answer: For the vast majority of embedding models, use dot product on normalized (unit-length) vectors. On unit-length vectors, cosine, dot product, and Euclidean distance all produce the same ranking of nearest neighbors, so you should pick the cheapest one to compute — and that's dot product. The metric only changes results when your vectors are not normalized and magnitude carries meaning.
The key fact: on normalized vectors, all three rank identically
If every vector has length 1 (L2 norm = 1), the three metrics are mathematically tied to each other:
- Cosine similarity is
dot(a, b) / (|a| * |b|). When|a| = |b| = 1, the denominator is 1, so cosine equals dot product exactly. - Euclidean distance squared expands to
|a|² + |b|² - 2·dot(a, b) = 2 - 2·dot(a, b). It's a strictly decreasing function of the dot product, so sorting by smallest Euclidean distance gives the same order as sorting by largest dot product.
The practical consequence: on unit-length vectors, dot product equals cosine and all three metrics rank the same. Your top-k results are identical no matter which you pick.
This isn't a niche case. OpenAI's embedding endpoints return length-1 normalized embeddings by default, so for those vectors your metric choice does not change ranking at all. Many sentence-transformer models normalize as well (check normalize_embeddings / the model card).
The real decision: normalize or not, then pick the cheapest equivalent
Because the metrics collapse to the same ranking once vectors are normalized, the decision isn't really "which metric" — it's:
- Are my vectors normalized? If yes (or you normalize them), the metrics are interchangeable for ranking.
- Among equivalent metrics, which is cheapest? Dot product.
So the default recipe is: normalize your embeddings, then configure your vector DB to use inner/dot product.
When magnitude matters (unnormalized vectors)
The metrics genuinely diverge only when vectors are not unit length and their magnitude means something:
- Cosine ignores magnitude entirely — it measures angle only. Two vectors pointing the same direction score 1.0 regardless of length.
- Dot product rewards magnitude: a longer vector in the same direction scores higher. This is why some models (e.g. certain retrieval models with learned document-length signals) intentionally leave vectors unnormalized so dot product can encode "importance" or confidence.
- Euclidean responds to both direction and absolute position in space.
If your model's docs say it was trained with dot product on unnormalized vectors, do not normalize — you'd be throwing away signal the model deliberately encoded. When in doubt, follow the model card, not a blog default.
Performance angle: dot product is the cheapest
At scale, the per-comparison cost adds up across millions of vectors:
- Dot product is just multiply-accumulate:
sum(a[i] * b[i]). - Cosine adds two norm computations and a division (unless you precompute norms).
- Euclidean adds a subtraction per dimension and a square root.
Dot product uses fewer operations than Euclidean — no subtraction and no square root — making it the cheapest at scale. And critically, you can pay the normalization cost once at write time: normalize each vector as you insert it, store the unit vector, and every subsequent query is a plain dot product. You never re-normalize at query time.
import numpy as np
def normalize(v):
return v / np.linalg.norm(v)
# At WRITE time: normalize once, store unit vectors
stored = np.stack([normalize(v) for v in raw_embeddings])
# At QUERY time: plain dot product == cosine == Euclidean ranking
q = normalize(query_embedding)
scores = stored @ q # cheapest possible
top_k = np.argsort(-scores)[:k]Config checklist per vector DB
Set the metric to match how your vectors are stored (and what your model expects):
- pgvector:
vector_ip_ops(inner product) for normalized vectors;vector_cosine_opsif unsure;vector_l2_opsfor Euclidean. - Qdrant:
Distance.DOTfor normalized,Distance.COSINEotherwise,Distance.EUCLIDfor L2. (Cosine in Qdrant auto-normalizes at insert.) - Pinecone:
metric="dotproduct"(normalize yourself),"cosine", or"euclidean". - Milvus / Weaviate / FAISS: expose
IP,COSINE, andL2equivalents —IndexFlatIPin FAISS expects pre-normalized vectors for cosine-equivalent results.
One rule ties it together: the metric must match how the model was trained. OpenAI and most sentence-transformers → normalized → dot product. A handful of retrieval models → unnormalized → dot product on purpose. Check the model card first.
Bottom line: normalize your embeddings once at write time and use dot product — it's mathematically identical to cosine and Euclidean for ranking, and it's the fastest.
Related: more writing.
Frequently asked
- Is cosine or dot product better?
- For normalized (unit-length) vectors they produce identical rankings, so use dot product — it's cheaper to compute (no norms or division).
- When does the similarity metric actually matter?
- Only with unnormalized vectors: cosine ignores magnitude while dot product and Euclidean both respond to it, so rankings can differ.
- Do I need to normalize embeddings?
- If your model doesn't already output unit vectors, normalizing once at write time lets you safely use fast dot product. But don't normalize if the model was trained to use magnitude as signal.
- Does OpenAI's embedding metric choice matter?
- No. OpenAI returns length-1 normalized embeddings, so cosine, dot product, and Euclidean all rank results identically — pick dot product for speed.
Keep reading
From Cloud Qdrant RAG to On-Device RAG: What Actually Transfers and What Doesn't
Moving a production cloud RAG pipeline on-device: the shape transfers, but index scale, embedder size, and re-ranking budgets do not.
ReadRAG chunking strategies: a practical reference
The common chunking approaches for retrieval, when each one helps, and the tradeoffs — a quick reference you can come back to.
ReadWhy RAG fails in production — and where
When RAG breaks, the model usually isn't the problem — retrieval is, about 73% of the time. Here's the real failure map and what to fix first.
ReadGet new essays by email
Occasional field notes on production RAG and LLM systems. No spam, unsubscribe anytime.