Vector space intuition
AssumesEmbeddings
In one line
Direction carries meaning and length usually does not, which is why almost every similarity measure you will use is an angle.
Why it exists
Once text is a vector, every question you want to ask becomes a question about geometry: what is near this, what is far, what sits between two things. That translation is only useful if you know which geometric quantity corresponds to the thing you care about — and in embedding spaces it is almost always the angle, almost never the distance.
Getting this wrong is not a subtle quality issue. It produces a search that ranks long documents above relevant ones, and it is invisible unless you look at the vectors themselves.
Direction carries meaning, length carries frequency#
Two vectors can point in nearly the same direction and have very different lengths. In embedding spaces produced by a text encoder, direction tends to encode what the text is about, while magnitude tends to correlate with things you do not care about: how long the passage is, how common its vocabulary is, how confidently the model classified it.
This is why is the default. It divides out both lengths and leaves only the angle. does not, so a short precise passage and a long rambling one about the same subject can land far apart under euclidean and adjacent under cosine.
The fix that makes the distinction disappear is : divide every vector by its own length so they all sit on the unit sphere. After that, cosine, and euclidean ranking all produce the same ordering, and an index is free to use whichever is fastest on the hardware. Normalise once at write time; doing it per query is wasted work and a source of drift when someone forgets.
What high-dimensional space is actually like#
Intuition built in two or three dimensions misleads badly at 768. Three properties matter in practice.
Almost everything is nearly orthogonal. Pick two random vectors in a high-dimensional space and their cosine similarity is very close to zero. This is good news: it means the space has enormous room for distinct concepts without collision, and a similarity of 0.4 is already a strong signal even though it looks small.
Distances concentrate. As dimensionality rises, the distance between the nearest and furthest points in a random set shrinks toward each other in relative terms. Absolute similarity scores lose meaning and only ranking survives. This is the mathematical reason a fixed threshold like "accept anything above 0.8" transfers so poorly between models and corpora.
Exact search is a full scan. No index structure partitions high-dimensional space the way a B-tree partitions an ordered key range; the partitions have too many neighbours. Every practical is therefore an structure, and "approximate" means it can legitimately miss a match. Correctness becomes a number you measure rather than a property you assume.
Where the geometry stops working#
The geometry is an approximation of meaning, and it fails in specific, predictable places.
Negation barely moves a vector. "The deployment succeeded" and "the deployment did not succeed" differ by one token and sit very close together, because most of the sentence is identical and embeddings average over the whole passage. If polarity matters to your application, the embedding will not carry it.
Exact identifiers have no useful position. An error code, an order number or a rare surname was probably split into meaningless fragments, and its vector reflects the fragments rather than the identity. This is the single strongest argument for : run alongside the vector query, because keyword matching is exactly right for the cases where geometry has nothing to say.
And numbers do not order themselves. "400" are not reliably further apart than "45". Filtering and range queries belong in metadata columns beside the vector, not in the vector.
The math
The dot product of two vectors, and cosine as the length-normalised version of it:
Take and — the same direction, different lengths:
Perfect similarity, as it should be: they point the same way. Euclidean distance between them is 5, which would rank them as quite different. Same pair of vectors, opposite conclusions, depending on which measure you picked.
Worked example
You index two chunks and query them. Vectors, kept to two dimensions so the arithmetic is visible:
query "how do I rotate an API key" -> [0.60, 0.80] length 1.00
chunk A "rotating API credentials" -> [0.66, 0.75] length 1.00
chunk B "API key rotation, a 4,000-word
guide with extensive preamble" -> [1.71, 2.10] length 2.71
Chunk B is the better answer and its vector is long, because it is a long document. Compare:
cosine(query, A) = (0.396 + 0.600) / (1.00 x 1.00) = 0.996
cosine(query, B) = (1.026 + 1.680) / (1.00 x 2.71) = 0.999
euclid(query, A) = sqrt(0.06^2 + 0.05^2) = 0.078
euclid(query, B) = sqrt(1.11^2 + 1.30^2) = 1.709
Cosine ranks B first. Euclidean ranks it a distant second, purely because it is longer. Normalising both vectors before indexing makes the two measures agree and removes the trap entirely.
Gotchas
-
Comparing similarity scores across models or corpora. The absolute numbers are artefacts of a particular space. A 0.72 from one model can be a strong match and a 0.72 from another can be noise. Fix thresholds per index, against labelled data, and re-derive them whenever the embedding model changes.
-
Using euclidean distance on unnormalised vectors. It silently ranks by length as much as by meaning. Either normalise at write time or configure the index for cosine — but do one of them deliberately rather than inheriting whatever the client library defaults to.
-
Expecting negation, quantity or exact identifiers to work. These are the three families of query where geometry has nothing to contribute. Route them to keyword search or to a metadata filter, and stop trying to fix them with a better embedding model.
-
Treating approximate recall as a tuning detail. An index at 80% recall is losing one relevant document in five before the model ever sees it, and the symptom is "the AI doesn't know things" rather than a search error. Measure recall against a brute-force scan on a sample, and treat it as a service level objective.
Mental model
Think of it as a cache keyed by meaning rather than by bytes. A normal cache answers "have I seen exactly this?" A vector space answers "have I seen something pointed in roughly this direction?" — and like any cache built on a lossy key, the engineering is all in understanding precisely which differences the key throws away. Length, negation and identity are the three it throws away here.
Done reading?
Nothing marks itself complete. Say so only when you could explain this to someone else.
This topic has 3 subtopics.