Interactive 3D visualization of vector relationships, angles, and similarity metrics.
Overview
Overview
How do we measure how "similar" two things are, especially when they are represented as lists of numbers (vectors)? While we could measure the distance between their endpoints (Euclidean distance), Cosine Similarity offers a different perspective: it measures the angle between two vectors. This simple but powerful idea is fundamental to modern AI, from search engines to recommendation systems. This 3D lab lets you build an intuition for what that angle really means.
Vectors
X
Y
Z
X
Y
Z
Presets
3D Space
Drag Tips
Metrics
Cosine Similarity
0.00cos(θ)
-101
Angle (θ)
90°Degrees
1.57Radians
Dot Product0.0
A · B
Formula:
similarity = (A · B) / (||A|| ||B||)
Measures the cosine of the angle between two vectors.
Understanding Cosine Similarity
How do we measure how "similar" two things are, especially when they are represented as lists of numbers (vectors)? While we could measure the distance between their endpoints (Euclidean distance), Cosine Similarity offers a different perspective: it measures the angle between two vectors. This simple but powerful idea is fundamental to modern AI, from search engines to recommendation systems. This 3D lab lets you build an intuition for what that angle really means.
The Core Idea: It's All About Direction
Imagine two arrows starting from the same point (the origin). Cosine similarity doesn't care about the length of these arrows (their "magnitude"); it only cares about the direction they are pointing.
If the vectors point in the exact same direction, the angle between them is 0°, and their cosine similarity is +1. They are considered identical in orientation.
If the vectors are perpendicular (at a 90° angle), they are considered unrelated or "orthogonal," and their cosine similarity is 0.
If the vectors point in opposite directions (180°), they are considered diametrically opposed, and their cosine similarity is -1.
This focus on direction is why cosine similarity is so useful. For example, in text analysis, the documents "the cat sat on the mat" and "a cat was sitting on a mat" are different in length and exact wording, but their vector representations will point in very similar directions, resulting in a high cosine similarity score.
Why angle rather than distance
Here is the situation cosine similarity was invented for. Take two documents about football. One is a 200-word match report; the other is a 4,000-word essay on the same match. Count the words in each and you get two vectors: the essay's numbers are all roughly twenty times bigger.
Measure the straight-line distance between those two vectors and they look completely different, because one is much longer than the other. But they are about the same thing, in the same proportions — roughly the same share of "goal", "keeper", "penalty". Cosine similarity ignores the lengths and compares only the directions, so the two documents come out nearly identical.
That is the whole idea: length is loudness, direction is meaning. Cosine similarity throws away the loudness.
The output always sits between −1 and 1:
1.0 — the vectors point the same way. Same mix of features, whatever the magnitudes.
0.0 — they are at right angles. No shared direction at all; in text, no words in common.
−1.0 — they point in exactly opposite directions.
With word counts or TF-IDF vectors no value can be negative, so scores land between 0 and 1. With learned embeddings, which do have negative components, the negative half of the range becomes reachable — though in practice most unrelated pairs cluster near 0 rather than at −1.
Two vectors, worked through
Take three words as the whole vocabulary — goal, keeper, tax — and two documents:
A = [3, 2, 0] — a short match report.
B = [30, 20, 0] — a long essay on the same match.
The dot product is 3×30 + 2×20 + 0×0 = 90 + 40 = 130.
The lengths are √(9 + 4 + 0) = 3.61 and √(900 + 400 + 0) = 36.06.
So the similarity is 130 / (3.61 × 36.06) = 130 / 130.2 = 1.00. Identical direction, exactly as expected: B is A multiplied by ten.
Now add C = [0, 1, 5], a piece about tax policy that mentions a keeper once. Against A: dot product = 0 + 2 + 0 = 2; lengths 3.61 and 5.10; similarity = 2 / 18.4 = 0.11. Almost nothing in common, which is the right answer.
Compare that with Euclidean distance: A to B is 33.3, A to C is 5.9. By distance, the tax article is five times "closer" to the match report than the essay about the same match is. That single comparison is the entire argument for using cosine in text.
Where you meet it in real systems
Search and RAG. A question and a stored paragraph are both turned into embedding vectors; the paragraphs with the highest cosine similarity to the question are the ones fed to the language model. Every vector database you have heard of is, at heart, a machine for finding high cosine similarity quickly.
Recommendations. Represent each user by what they watched and each film by who watched it, then recommend the films whose vectors point in a similar direction to the user's.
Duplicate detection. Support tickets, product listings and news articles above about 0.9 similarity are usually the same thing said twice.
Face and voice matching. A face recogniser turns an image into a vector; two photographs of the same person land at similarity around 0.7–0.9, two different people much lower. The system is a threshold on a cosine.
Clustering text. Cosine distance (1 − similarity) is the usual distance for grouping documents, because raw length should not decide which cluster a document joins.
Cosine, dot product and Euclidean
Measure
Sensitive to length?
Range
Use it when
Cosine similarity
No
−1 to 1
Comparing meaning or composition, especially text
Dot product
Yes
Unbounded
Length carries real information, e.g. confidence or popularity
Euclidean distance
Yes
0 upwards
Coordinates in real space, where distance is literal
Manhattan distance
Yes
0 upwards
Grid-like movement, or many sparse dimensions
Two facts worth carrying around. First, on normalised vectors — ones scaled to length 1 — the dot product and cosine similarity are the same number, which is why embedding libraries normalise on the way in and then use fast dot products. Second, on normalised vectors, ranking by cosine similarity and ranking by Euclidean distance give the same order, because squared distance = 2 − 2×cosine. The choice only matters when the vectors are not normalised.
In code, and the trap in it
import numpy as np
def cosine(a, b):
a, b = np.array(a, float), np.array(b, float)
denom = np.linalg.norm(a) * np.linalg.norm(b)
return float(a @ b / denom) if denom else 0.0 # guard the zero vector
cosine([3, 2, 0], [30, 20, 0]) # 1.0
cosine([3, 2, 0], [0, 1, 5]) # 0.108...
The guard on the last line is not pedantry. An empty document, a row of all zeros after filtering, or a user who has clicked nothing all produce a zero vector, and dividing by its length is a division by zero. Left unhandled it becomes a nan that quietly poisons every average downstream.
For anything bigger than a toy, use sklearn.metrics.pairwise.cosine_similarity, which takes two matrices and returns every pairwise score at once, or normalise your vectors once and use a dot product — a matrix multiply is orders of magnitude faster than a Python loop over pairs.
Questions people ask
Is high cosine similarity the same as "means the same thing"? Only as far as the vectors are good. On raw word counts, "the match was excellent" and "the match was awful" score highly because they share three words out of four. Modern sentence embeddings handle this far better, but the similarity is always a statement about the representation, not about truth.
What counts as a "high" score? It depends on the embedding model, so calibrate rather than guess. Score a few hundred pairs you know are related and a few hundred you know are not, then look at where the two distributions separate. For many sentence embedding models unrelated text still scores 0.2–0.4, so a threshold of 0.5 lets a lot of noise through.
Why do all my scores look high? Some embedding models pack everything into a narrow cone of the space, so absolute values bunch up. Relative ranking still works: care about which pairs score highest, not whether the number crosses a round threshold.
Does it work with more than three dimensions? The formula is identical for 768 or 3,072 dimensions — sum the products, divide by the two lengths. Only the picture in your head stops at three.
Should I remove very common words first? With plain counts, yes: "the" appearing everywhere inflates every score. TF-IDF weighting does this automatically by discounting words that appear in most documents. Learned embeddings handle it internally.
Recap in one screen
Cosine similarity measures the angle between two vectors and ignores their lengths.
The formula is the dot product divided by both lengths; the result runs from −1 to 1.
It is the default for text, embeddings, search and recommendations, because a longer document should not be a different document.
On length-1 vectors it equals the dot product, and it ranks identically to Euclidean distance.
Calibrate your threshold against known pairs, and always guard against zero-length vectors.
How It's Calculated
The similarity score is literally the cosine of the angle ($\theta$) between the two vectors, $\vec{A}$ and $\vec{B}$. The formula looks like this:
Similarity = cos($\theta$) = (A · B) / (||A|| * ||B||)
Let's break that down:
A · B (The Dot Product)
This measures how much the vectors "agree." You calculate it by multiplying the corresponding components of each vector and summing the results. (A.x * B.x) + (A.y * B.y) + ...
||A|| and ||B|| (The Magnitudes)
This is the length of each vector, calculated using the Pythagorean theorem. By dividing by the magnitudes, we "normalize" the vectors, effectively ignoring their lengths and focusing only on their direction.
The metric that ignores how long a vector is
Cosine similarity measures angle, not distance. That single property is why it is the default for text and embeddings, and where it goes wrong.
example_01.pyscikit-learn
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances
a = np.array([[1.0, 2.0, 3.0]])
b = np.array([[2.0, 4.0, 6.0]]) # exactly 2x a
c = np.array([[3.0, 2.0, 1.0]]) # same length as a, different direction
print("a =", a[0], " b = 2a =", b[0], " c =", c[0])
print()
print("%-28s %12s %12s" % ("", "cosine sim", "euclidean"))
for name, v in (("a vs b (same direction)", b), ("a vs c (same length)", c)):
print("%-28s %12.6f %12.6f"
% (name, cosine_similarity(a, v)[0, 0], euclidean_distances(a, v)[0, 0]))
print()
print("cosine calls a and b identical. they point the same way; one is just")
print("longer. euclidean distance calls them 3.74 apart, because it measures")
print("exactly that length difference.")
print()
print("computed by hand, so there is nothing hidden:")
print(" dot(a, b) = %.4f" % a[0].dot(b[0]))
print(" |a| = %.6f, |b| = %.6f" % (np.linalg.norm(a), np.linalg.norm(b)))
print(" dot / (|a||b|) = %.6f"
% (a[0].dot(b[0]) / (np.linalg.norm(a) * np.linalg.norm(b))))
print()
print("the range, and what each end means:")
for name, u, v in (("identical direction", [1, 1], [3, 3]),
("perpendicular", [1, 0], [0, 5]),
("opposite", [1, 2], [-2, -4]),
("partly aligned", [1, 0], [1, 1])):
print(" %-22s %+.4f" % (name, cosine_similarity([u], [v])[0, 0]))
print(" -1 to +1 for arbitrary vectors. for counts and TF-IDF, which are")
print(" never negative, it is 0 to 1 -- there is no way to be 'opposite'.")
print()
print("why text uses it. two documents on the same subject, different lengths:")
vocab = ["model", "train", "data", "loss", "cat"]
short = np.array([2.0, 1.0, 2.0, 1.0, 0.0])
long_ = short * 20
other = np.array([0.0, 0.0, 1.0, 0.0, 9.0])
for name, v in (("the same doc, 20x longer", long_), ("a doc about cats", other)):
print(" %-26s cosine %.6f euclidean %8.4f"
% (name, cosine_similarity([short], [v])[0, 0],
euclidean_distances([short], [v])[0, 0]))
print(" euclidean would rank the cat document as the closer match, purely")
print(" because it is a similar length. cosine is not fooled.")
print()
print("the connection worth knowing: on unit-length vectors the two are the")
print("same ranking. euclidean^2 = 2 - 2*cosine, exactly.")
rng = np.random.default_rng(0)
v = rng.normal(size=(5, 8))
v = v / np.linalg.norm(v, axis=1, keepdims=True)
d2 = euclidean_distances(v[:1], v[1:])[0] ** 2
cs = cosine_similarity(v[:1], v[1:])[0]
print(" euclidean^2 :", np.round(d2, 6))
print(" 2 - 2*cosine:", np.round(2 - 2 * cs, 6))
print()
print("so if your vectors are normalised -- and most embedding models return")
print("them normalised -- cosine and euclidean give identical neighbours, and")
print("a vector database can use whichever is faster.")
print()
print("where it fails: when magnitude is the signal. two users who rated the")
print("same films the same way but one rated 4 films and one rated 400 look")
print("identical to cosine, and they are not the same kind of user.")
Output
Try it yourself
Use the controls and presets to explore the concept.
Explore the Presets: Click the "Parallel", "90°" (Orthogonal), and "Opposite" buttons. Watch how the vectors align in the 3D space and see how the Angle and Cosine Similarity metrics update to 0° (+1), 90° (0), and 180° (-1) respectively.
The Effect of Magnitude: Set Vector A to (1, 1, 0) and Vector B to (2, 2, 0). They are perfectly parallel, so the similarity is +1. Now, change Vector B to (5, 5, 0). The length of Vector B changes dramatically, but its direction doesn't. Notice that the angle remains 0° and the cosine similarity stays at +1. This is the key takeaway: magnitude doesn't matter.
Create "Almost Similar" Vectors: Start with Vector A as (1, 1, 0) and Vector B as (1, 1, 0). Now, slightly nudge one of Vector B's components, for example, change its Y value to 1.2. The vectors are no longer perfectly aligned. Observe that the angle is now small (but not zero) and the cosine similarity is high (but not exactly 1). This is what happens in real-world applications like document similarity.
Summing up
It's a Measure of Direction, Not Size: Cosine similarity ignores the magnitude (or length) of the vectors and only considers their orientation in space.
The Range is [-1, 1]: A score of +1 means identical direction, 0 means they are unrelated (orthogonal), and -1 means they are exact opposites.
Foundation for NLP: This is the primary way we measure the similarity between words, sentences, and entire documents after they have been converted into vector embeddings (like Word2Vec or BERT embeddings).
Powers Recommendation Engines: It's used to find similar users or items. If your vector of movie ratings is similar to someone else's, the engine might recommend movies you haven't seen that they liked.
Recall check
0 of 3
Say the answer out loud before you reveal it — recalling it is what makes it stick, and rereading it is not.
What does this module say about “Overview”?
How do we measure how "similar" two things are, especially when they are represented as lists of numbers (vectors)? While we could measure the distance between their endpoints (Euclidean distance), Cosine Similarity offers a different perspective: it measures the angle between two vectors. This simple but powerful idea is fundamental to modern AI, from search engines to recommendation systems.
What does this module say about “The Core Idea: It's All About Direction”?
Imagine two arrows starting from the same point (the origin). Cosine similarity doesn't care about the length of these arrows (their "magnitude"); it only cares about the direction they are pointing.
What does this module say about “Why angle rather than distance”?
Here is the situation cosine similarity was invented for. Take two documents about football. One is a 200-word match report; the other is a 4,000-word essay on the same match. Count the words in each and you get two vectors: the essay's numbers are all roughly twenty times bigger.
Cheat sheet
Cosine Similarity Metric
How do we measure how "similar" two things are, especially when they are represented as lists of numbers (vectors)? While we could measure the distance between their endpoints (Euclidean distance), Cosine Similarity offers a different perspective: it measures the angle between two vectors. This simple but powerful idea is fundamental to modern AI, from search engines to recommendation systems. This 3D lab lets you build an intuition for what that angle really means.
Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.