Embeddings & Vector Search· Lesson 2 of 7
Measuring Vector Similarity
In one line · To tell how close two texts are in meaning, you measure the distance or angle between their vectors — most commonly with cosine similarity.
This is the core that ranks every semantic-search result: the metric decides what rises to the top and what sinks, so getting it wrong breaks the whole search.
Cosine looks at direction, not length: two vectors pointing the same way are similar even if one is longer. The score runs from -1 (opposite) to 1 (same meaning).
Like two compass needles: you don't care that one is longer, only whether both point the same way. Cosine measures exactly that direction and ignores the length.
The query 'how do I restart the server' scores high cosine with an article on 'rebooting the host' and low with 'koshari recipe' — so you show the first and drop the second.
Cosine similarity is the dot product of the two vectors divided by the product of their lengths — that strips out magnitude and keeps only direction. Other metrics exist: raw dot product (sensitive to length) and Euclidean distance (straight-line gap). Many models output normalized vectors (length 1), in which case cosine and dot product give the exact same ranking. Practical rule: use the metric the model was trained for — the model card tells you.
// Cosine similarity between two vectors
function cosine(a: number[], b: number[]): number {
let dot = 0, na = 0, nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
return dot / (Math.sqrt(na) * Math.sqrt(nb));
}
// Near 1 = same meaning, near 0 = unrelated🧪 Try it: write one query and 5 sentences (2 close to it, 2 unrelated, 1 borderline). Embed them all with the same model, compute cosine between the query and each sentence using the function above, and sort descending. Check whether the ranking matches your intuition — the surprises are what teach you the model's limits.
The biggest mistake: reading cosine as an absolute percentage — '0.8 means 80% similar.' Scores are relative per model: 0.8 can be a strong match in one model and mediocre in another; tune your threshold empirically on your own data. Second: mixing metrics — storing in an index configured for cosine and querying with Euclidean. Third: comparing similarity scores produced by two different models.
Use cosine as the default for text similarity — it fits when meaning matters more than 'intensity,' and most embedding models are trained for it (or for dot product with normalization).
If your model was trained on unnormalized dot product (as some recommendation models are), the magnitude itself carries signal — use dot product as the model expects. And for things that aren't embeddings at all (coordinates, physical measurements), Euclidean distance is usually the better fit.