In Topic 07, we learned how text becomes vectors and how cosine similarity compares them. Now we'll use those ideas to build a small Netflix-style search system.
Imagine typing:
“A lost astronaut trying to get home.”
A normal keyword search might fail to find The Martian because its description talks about being left behind on Mars and surviving until rescue. The meaning matches, but the exact words don't.
Semantic search solves this by searching for meaning instead of matching only words.
1. The big picture
The key idea: embed the catalogue once, then embed only the user's query for each search.
2. Embedding model vs chat model
These two models have different jobs.
Chat model: writes
- Input: “What is the capital of India?”
- Output: “New Delhi.”
- Use it for explanations, summaries, and generating replies.
Embedding model: represents
- Input: “What is the capital of India?”
- Output:
[0.023, -0.193, 0.082, ...] - Use it for semantic search, matching, grouping, and ranking.
An embedding model doesn't answer the question. It gives the input a position in a learned vector space.
| Chat model | Embedding model | |
|---|---|---|
| Output | Generated text | Fixed-length vector |
| Main job | Write | Represent meaning |
| Typical use | Summarise a ticket | Find similar tickets |
| Output generation | Token by token | One vector per input |
The course uses OpenAI's text-embedding-3-small, which returns 1,536 numbers by default. One word, one sentence, or one paragraph produces a vector of the same length.
3. Why not send the whole catalogue to a chat model?
A beginner might try this:
Here are all 8,000 films and their descriptions.
The user wants: a lost astronaut trying to get home.
Choose the three best films.It may work for a small list, but it creates four problems.
- Context limit: The whole catalogue may not fit into one model request. The course estimates that 8,000 films at roughly 50 tokens each would need about 400,000 input tokens.
- Repeated work: Every search sends the same film descriptions again, even though the catalogue hasn't changed.
- Wrong operation: We mainly need scores and a ranking, not a long generated answer. Cosine similarity and sorting can do that directly.
- Cost and latency: The chat model must read a huge prompt and generate text for every search. Embedding the short query and comparing vectors is much lighter.
Think of a shop cashier. It would be inefficient to read the entire price list aloud to every customer. Record the prices once, then look up what each customer needs.
Engineering lesson: Choose the operation you need, not simply the most powerful model.
- Need to write or explain? Use a chat model.
- Need to find similar items? Use embeddings and similarity scoring.
- Need to filter films released after 2010? Use a database query.
- Need to add an order total? Use normal code.
4. Precompute once, reuse many times
A film's description rarely changes, but users' searches change constantly. So separate stable data from changing data.
A batch call sends multiple descriptions in one API request instead of making a separate request for every film.
For example, if you have 30 films, you send 30 descriptions in one batch and receive 30 vectors. Save each vector alongside its corresponding film.
Recompute vectors when:
- A film's description changes: re-embed that film.
- You switch embedding models: re-embed the whole catalogue so all vectors belong to the same space.
For a small demo, vectors can stay in memory. For a larger catalogue, store them in a file or database, or use a vector database.
5. Semantic search in action
Suppose the query is:
a lost astronaut trying to get home
A keyword search might rank results like this:
| Film | Why keyword search finds it |
|---|---|
| Toy Story | Description contains “lost” and “home” |
| Apollo 13 | Description mentions bringing astronauts home |
| The Martian | May be missed because it uses different wording |
The Martian is arguably the most relevant match, even if the exact query words aren't present in its description.
Semantic search works differently:
- Convert the query into a vector.
- Compare it with every stored film vector using cosine similarity.
- Sort the films from highest score to lowest.
- Return the top K results.
For example, the course's illustrative scores are:
| Rank | Film | Cosine similarity |
|---|---|---|
| 1 | The Martian | 0.988 |
| 2 | Gravity | 0.965 |
| 3 | Interstellar | 0.935 |
| 4 | Ocean's Eleven | 0.331 |
| 5 | Notting Hill | 0.237 |
These scores are from a simplified, hand-readable example, not actual API results. Real scores depend on the embedding model and the input text. The ranking is what matters here.
What does Top K mean?
K is the number of results you want to return.
If K = 3, return the three highest-scoring films. The course demo allows K from 1 to 10, defaulting to 3.
Here's the core ranking logic in Node.js:
export function rank(queryVector, candidates, k) {
return candidates
.map(({ movie, vector }) => ({
title: movie.title,
year: movie.year,
genre: movie.genre,
score: cosineSimilarity(queryVector, vector),
}))
.sort((a, b) => b.score - a.score) // Highest first
.slice(0, k); // Keep top K
}This function doesn't need a chat model. It calculates scores, sorts the list, and returns the best matches.
6. Build “More like this”
Now imagine a user finishes watching Gravity. The app displays a More like this row.
We don't need to embed a new text query because we already have Gravity's stored vector.
The steps are:
- Find Gravity in the catalogue.
- Retrieve its stored vector.
- Compare it with the other film vectors.
- Exclude Gravity itself.
- Return the top K matches.
The course's illustrative results are:
| Rank | Film | Similarity to Gravity |
|---|---|---|
| 1 | Interstellar | 0.995 |
| 2 | The Martian | 0.990 |
| 3 | Notting Hill | 0.459 |
| 4 | Ocean's Eleven | 0.255 |
Gravity itself must be excluded because a vector compared with itself has cosine similarity of 1. Otherwise, the first recommendation would be the film the user has already watched.
Important: Search and recommendation use the same ranking function. The only difference is the starting vector.
- Search starts with a new vector created from the user's query.
- “More like this” starts with a vector already stored for a film.
The second feature needs no embedding API call, so it uses no additional embedding tokens.
7. Content-based vs collaborative filtering
These are two different ways to recommend items.
Content-based recommendation
- Looks at what the item is about.
- Example: “You liked Gravity, so here are other space-survival films.”
- Needs item descriptions or other content features.
Collaborative filtering
- Looks at what people watched, rated, or purchased.
- Example: “People who watched this film also watched that film.”
- Needs user behaviour or interaction history.
| Scenario | Content-based | Collaborative |
|---|---|---|
| What it uses | Item content | User behaviour |
| New film with no viewing history | Can recommend it if its content is available | Has little or no history to use |
| Course demo | Yes | No |
The problem of a new item having no interaction history is called the cold start problem.
Real recommendation systems often combine content similarity, watch history, ratings, popularity, freshness, and business rules rather than relying on one signal alone.
8. How the Node.js service is structured
The course demo has two endpoints:
| Endpoint | Purpose |
|---|---|
GET /api/movies/search?q=lost%20astronaut&k=3 | Find films matching a text query by meaning |
GET /api/movies/similar?title=Gravity&k=3 | Find films similar to Gravity |
A search request follows this path:
The HTTP layer validates inputs before calling the embedding API. This prevents an empty query or invalid K from spending money unnecessarily.
The course defines these response statuses:
| Status | Meaning |
|---|---|
200 | Results returned |
400 | Empty query/title or invalid K |
404 | Requested film title not found |
502 | Embedding API failed |
The demo caches its index in memory. A production service should consider a persistent store because restarting the application otherwise loses the vectors.
It should also avoid building the same index twice when simultaneous requests arrive, and it should allow a failed index-building attempt to be retried rather than caching the failure forever.
9. The complete mental model
Memorise these four steps:
These same steps can power product search, similar support tickets, job-to-CV matching, recipe search, help-centre search, and code search.
Interview revision
"Why use embeddings instead of sending a catalogue to an LLM?"
To avoid repeatedly sending a huge prompt; precomputed vectors allow inexpensive scoring and ranking.
"Why precompute embeddings?"
Catalogue content changes infrequently, so computing vectors once avoids repeating the same work.
"Why use cosine similarity?"
To rank vectors by directional similarity.
"Why is “More like this” cheaper?"
It reuses the stored vector of the selected item.
"What is Top K?"
The number of highest-scoring results to return.
"What is the difference between semantic and keyword search?"
Semantic search compares meaning; keyword search looks for matching words.
"What is RAG?"
Retrieval-Augmented Generation; this course introduces it as a pattern where embedding search finds relevant items and a chat model can optionally generate a response from those results.
Quick quiz
1. What does an embedding model return?
- A written answer
- A fixed-length vector
- A sorted movie list automatically
2. Why precompute film embeddings?
- Film descriptions rarely change, so vectors can be reused
- To generate a new description every search
- To avoid storing film information
3. What does K mean in Top K?
- Number of embedding dimensions
- Number of API keys
- Number of results to return
4. What does the “More like this” feature do?
- Embeds the same film description every time
- Compares the selected film's stored vector with other films
- Asks a chat model to invent similar titles
5. Which recommendation method uses what other users watched or bought?
- Content-based recommendation
- Collaborative filtering
- Keyword filtering
Final takeaway: Don't ask a chat model to repeatedly read your entire catalogue just to pick a few items. Represent the catalogue as vectors, store them, embed the incoming query, compare, and rank. Use a chat model only if you also want it to explain the results in natural language.