What Are Embeddings? Vectors and Cosine Similarity Explained Simply
Embeddings let computers compare meaning instead of matching exact words. They power semantic search, recommendations and RAG. Learn what they are, how similarity is measured, and where they let you down.
- An embedding is a list of numbers that represents the meaning of text, an image or other data.
- Similar items end up with similar numbers, so meaning can be compared with math.
- Cosine similarity compares direction, and it is a common way to score how alike two embeddings are.
- Only compare embeddings from the same model, and test results on your own data.
Search for “how to get my money back”, and a good system should find a page titled “Refund policy”, even though the two share no important words. That ability comes from embeddings, one of the most useful ideas in modern AI.
This guide explains embeddings without heavy maths. We ran a small example in Python 3.13, and its output is shown below. The vectors in it are ones we made up by hand, with only three numbers each, to make the idea easy to see. Real embeddings come from a trained model and often have hundreds or thousands of numbers.
What is an embedding?
An embedding is a representation of something as a list of numbers, called a vector. A model is trained so that items with related meanings get vectors that are close together.
"kitten" -> [0.9, 0.8, 0.1]
"cat" -> [0.9, 0.7, 0.2]
"invoice" -> [0.0, 0.1, 0.9]
Once text is in this form, a computer can calculate how similar two pieces of text are, which it cannot do directly with words. Embeddings are not limited to words, either. Sentences, whole documents, images, audio and products can all be embedded.
Why not just match keywords?
| Query and document | Keyword search | Embedding search |
|---|---|---|
| “money back” and “refund policy” | Misses it, no shared words | Can find it |
| “laptop” and “notebook computer” | Misses it | Can find it |
| “error E1042” and a page with “E1042” | Finds it exactly | May miss it |
| “bank” (river) and “bank” (money) | Treats them alike | Can separate them, given context |
Our guide to RAG shows this in practice. A simple word-count search scored zero for “When can I get my money back?” against a refunds document, since they shared no words. Note the third row as well: exact identifiers are a weakness of embeddings, so keyword search still has a place.
Think of a map
Imagine placing every word on a huge map, where location reflects meaning. “Cat” and “kitten” sit almost next to each other, “dog” is in the same neighbourhood, and “invoice” is in a different city. To find related words, you simply look nearby.
A real embedding works the same way, except that the map has hundreds of dimensions rather than two. People cannot picture that, but the math works just the same. Individual numbers generally have no neat meaning, like “furriness”. What matters is the overall position.
A small working example
Here is how to compare our hand-made vectors:
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
return dot / (math.sqrt(sum(x * x for x in a)) *
math.sqrt(sum(y * y for y in b)))
vectors = {
"kitten": [0.9, 0.8, 0.1],
"cat": [0.9, 0.7, 0.2],
"dog": [0.8, 0.3, 0.2],
"invoice": [0.0, 0.1, 0.9],
}
for word in ("kitten", "dog", "invoice"):
print(word, round(cosine(vectors["cat"], vectors[word]), 3))
# kitten 0.994
# dog 0.955
# invoice 0.238
Against “cat”, “kitten” scores highest, “dog” next, and “invoice” far lower. We chose these vectors to produce that pattern, so the ranking proves nothing about real language. It only shows how the calculation behaves. A semantic search does the same thing in three steps: embed the query, score it against every stored item, and sort.
query = [0.85, 0.75, 0.15]
sorted(vectors, key=lambda w: cosine(query, vectors[w]), reverse=True)
# ['kitten', 'cat', 'dog', 'invoice']
Cosine similarity explained
Each vector can be thought of as an arrow pointing from the origin. Cosine similarity measures the angle between two arrows, ignoring how long they are. We confirmed the key values:
cosine([1, 2, 3], [2, 4, 6]) # 1.0 same direction
cosine([1, 0], [0, 1]) # 0.0 unrelated (at right angles)
cosine([1, 0], [-1, 0]) # -1.0 opposite directions
| Score | Meaning |
|---|---|
| Close to 1 | Point in nearly the same direction, so very similar |
| Close to 0 | Unrelated |
| Close to -1 | Point in opposite directions |
Look at the first line. [2, 4, 6] is twice as long as [1, 2, 3], yet the score is a perfect 1.0, because direction is all that counts. That is useful for text, where length can reflect things like document size rather than meaning. Be careful with thresholds, too. There is no universal score that means “similar”, as useful ranges differ from model to model, so test on your own data.
Other distance measures
| Measure | What it looks at | Notes |
|---|---|---|
| Cosine similarity | Angle only | A common default for text |
| Dot product | Angle and length | Fast, and equals cosine on normalized vectors |
| Euclidean distance | Straight-line distance | Smaller means more similar |
The difference is easy to see. Those same two vectors with a cosine score of 1.0 have a Euclidean distance of 3.742, since one is longer than the other. Many systems normalize vectors, scaling each to a length of 1. After that, the dot product and cosine give the same answer, and we confirmed this: both gave 0.955 for “cat” and “dog”. Check your model’s documentation to see which measure it recommends.
How embeddings are learned
Nobody assigns these numbers by hand in real systems. A neural network learns them during training. An early and influential idea is that words appearing in similar contexts tend to have similar meanings, as with “I fed my cat” and “I fed my dog”. Models such as Word2Vec and GloVe learned word vectors from those patterns.
Later, transformer-based models such as BERT made embeddings context-aware, so “bank” could get a different vector in “river bank” and “bank loan”. Many modern models embed entire sentences or passages at once. Our guide to large language models explains how transformers handle context.
Where embeddings are used
| Use | How it helps |
|---|---|
| Semantic search | Finds results by meaning, rather than exact words |
| RAG | Retrieves relevant passages for a language model |
| Recommendations | Suggests items similar to those you liked |
| Clustering | Groups support tickets or reviews by topic |
| Duplicate detection | Spots similar questions or listings |
| Classification | Provides features for simple models |
| Anomaly detection | Flags items far from everything else |
Searching at scale
Our example compared a query with every item, which is known as an exact search. That works fine for thousands of items, but it becomes slow with many millions. Approximate nearest neighbour (ANN) indexes such as HNSW solve this. They find very close matches quickly, by giving up a small chance of missing the absolute best one.
Vector databases and extensions, such as pgvector for PostgreSQL, provide these indexes along with filtering and storage. Our vector database guide covers the options. For a small project, you may not need one at all, and an in-memory search is a perfectly good way to start.
Tip: Consider hybrid search early. Combining embedding similarity with keyword matching often gives better results than either alone, particularly when users search for product codes, names or error messages.
Choosing an embedding model
- Language. If your content is in Hindi or Tamil, for example, check that the model handles it well.
- Domain. General models can struggle with specialist legal or medical terms.
- Dimensions. More dimensions can capture more detail, but they cost more storage and memory.
- Cost and hosting. Compare a hosted API with an open model you can run yourself, especially for private data.
- Input length. Models have a limit, so long documents must be split into chunks first.
The best test is your own data. Collect about 20 real queries along with the documents they should find, and compare how each model ranks them. Published benchmarks may not reflect your content.
Limitations
- Model lock-in. Vectors from different models cannot be compared. If you change models, you must embed everything again.
- Exact terms. IDs, codes and rare names may not be matched reliably.
- Negation. “Refunds allowed” and “refunds not allowed” can land close together, despite meaning opposite things.
- Similar does not mean correct. A close match may be outdated or simply wrong.
- Bias. Embeddings can absorb stereotypes from their training data.
- Cosine is not magic. Researchers have questioned how meaningful it is on some learned embeddings, so treat scores as a useful signal and not as a measure of truth.
Common mistakes
- Mixing models. Embed queries and documents with the same model and version.
- Using one similarity threshold everywhere. Tune it for each model and task.
- Embedding huge documents whole. Meaning gets diluted, so chunk them first.
- Skipping evaluation. Without test queries, you cannot tell if a change helped.
- Using embeddings for everything. If users search for exact IDs, a normal database index is better.
Final thoughts
An embedding turns meaning into numbers, so that software can compare ideas rather than strings. The concept is simple, while the practical work lies in choosing a model, chunking data and testing on real queries. To try it yourself, embed a few dozen of your own documents with any embedding model, search them with real questions, and see where the results surprise you.
Frequently asked questions
What are embeddings in simple terms?
Embeddings are lists of numbers that represent meaning. Items with similar meaning get similar numbers, which lets a computer compare them mathematically.
What is cosine similarity?
It measures the angle between two vectors, from -1 to 1. A score near 1 means they point in nearly the same direction, which suggests similar content.
What is the difference between embeddings and tokens?
A token is a chunk of text that a model processes. An embedding is a numeric vector representing the meaning of a token, sentence or document.
How many dimensions should an embedding have?
It depends on the model, and hundreds to a few thousand is common. More can capture more detail, but it costs more storage, so test what your use case needs.
Do I need a vector database to use embeddings?
No. Small collections can be searched in memory. Vector databases become useful with larger collections, frequent updates or filtering needs.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.