Learning Recommendation Systems by Building a Hybrid Movie Recommender
How I built Movie Recommendation system to explore content-based filtering, collaborative filtering, and the machine learning concepts…
Learning Recommendation Systems by Building a Hybrid Movie Recommender
How I built Movie Recommendation system to explore content-based filtering, collaborative filtering, and the machine learning concepts behind modern recommendation systems.
Introduction
Every day, recommendation systems influence the choices we make without us even noticing. Whether it’s the next movie on Netflix, a playlist on Spotify, a video on YouTube, or a product on Amazon, these platforms constantly suggest content that matches our interests. Behind those suggestions are machine learning models designed to understand our preferences and recommend what we’re most likely to enjoy.
Like many people, I used these systems every day without thinking much about how they actually worked. Recently, I learned about terms like content-based filtering and collaborative filtering, but they were just concepts I had read about. I wanted to move beyond the theory and understand what really happens behind the scenes when a recommendation is generated.
That’s why I decided to build CineMatch, a hybrid movie recommendation system from scratch. Instead of relying on a single algorithm, I explored multiple recommendation approaches, trained and evaluated different models, compared their performance, and combined the best ideas into a hybrid recommendation engine. Along the way, I also learned how to handle real-world challenges such as sparse user interactions, cold-start users, and evaluating recommendation quality using ranking metrics.
This article is a walkthrough of that journey. Rather than focusing on the user interface, I’ll dive into the machine learning concepts, the backend implementation, the experiments I conducted, the lessons I learned, and how different recommendation techniques come together to build a practical movie recommendation system. Whether you’re just getting started with recommendation systems or looking for a real-world implementation, I hope this guide helps make these concepts easier to understand.
What I Wanted to Learn
Before I started building CineMatch, I set one simple goal: I didn’t want to just use recommendation algorithms but also wanted to understand how they actually work.
Instead of treating machine learning models as black boxes, I wanted to build them step by step, compare their strengths and weaknesses, and learn why one approach performs better than another in different situations.
Here are the key concepts I wanted this project to teach me:
- Collaborative Filtering — How can a system recommend movies by learning from the behavior of other users? I wanted to explore user-based filtering, item-based filtering, and matrix factorization to understand how each approach makes recommendations.
- Content-Based Filtering — What if a user is interested in movies with similar genres, plots, or cast members? I wanted to learn how techniques like TF-IDF and Cosine Similarity can recommend similar movies based on their content instead of user behavior.
- Hybrid Recommendation Systems — Both collaborative and content-based filtering have their own strengths and limitations. I wanted to learn how combining them could produce more accurate and reliable recommendations than using either model alone.
- Implicit Feedback — In real-world applications, users rarely rate every movie they watch. Instead, they leave behind signals like views, clicks, likes, or watch history. I wanted to understand how these interactions could be converted into meaningful training data for a recommendation model.
- Model Evaluation — Building a recommendation system is one thing, but knowing whether it actually works is another. I wanted to learn how to evaluate recommendation models using metrics such as Precision@K, Recall@K, Hit Rate, and F1-score, rather than relying only on traditional prediction metrics like RMSE.
- The Cold-Start Problem — One of the biggest challenges in recommendation systems is making good recommendations for new users with little or no interaction history. I wanted to explore practical strategies for handling this problem.
Throughout the project, I followed one rule: every recommendation model had to justify its existence.
The simplest model, a popularity-based recommender served as my baseline. If a more advanced model couldn’t outperform that baseline, I needed to understand why. Surprisingly, some of the simplest approaches performed better than I expected, while some more sophisticated models struggled. Those results became some of the most valuable lessons of the entire project because they showed that in machine learning, a more complex model isn’t always a better one.
Understanding Recommendation Systems
Before diving into the implementation, let’s first understand the core ideas behind recommendation systems. These concepts are the foundation of everything we’ll build later. If you’re already familiar with them, feel free to skim through this section. Otherwise, this will help you understand not just what the models do, but why they work.
The Popularity Baseline: Recommending What Everyone Likes
The simplest recommendation system doesn’t try to learn anything about individual users. Instead, it recommends the same list of popular movies to everyone.
For example, if thousands of users have highly rated movies like The Shawshank Redemption, The Godfather, or The Dark Knight, a popularity-based recommender simply shows those movies to every new user. It isn’t personalized, but it’s surprisingly effective because popular movies are generally enjoyed by a large audience. However, there’s one important problem.
Imagine these two movies:
- Movie A has one 5-star rating.
- Movie B has 20,000 ratings with an average of 4.8 stars.
If we simply sort by average rating, Movie A would appear above Movie B, even though only one person has watched it. Clearly, that isn’t a fair comparison.
To solve this, I used Bayesian Average Rating (Bayesian Smoothing).
Instead of trusting the average rating alone, Bayesian smoothing also considers how many ratings a movie has received. Movies with only a few ratings are pulled closer to the overall average rating of the dataset, while movies with thousands of ratings are trusted more because there is much stronger evidence that people genuinely like them.
You can think of it like this:
A movie should earn its high ranking through consistent ratings from many users, not just one perfect review.
This simple adjustment creates a much more reliable popularity ranking.

The most-rated movies in the dataset
Why Start with Such a Simple Model?
At first, it might seem strange to include such a basic recommendation system in a machine learning project. The reason is simple: it gives us a baseline to beat.
A popularity model is often stronger than people expect because it recommends movies that most users already enjoy. If a more advanced recommendation algorithm cannot outperform this simple baseline, then the extra complexity isn’t providing any real value.
That’s why I used the popularity model as the benchmark throughout this project. Every collaborative filtering and content-based model was compared against it to answer one important question:
Does this model actually recommend better movies than simply showing the most popular ones?
This simple baseline became an important reference point throughout the project, helping me understand when a more sophisticated model was genuinely improving recommendation quality and when it wasn’t.
Content-Based Filtering: Recommending Similar Movies
Unlike a popularity-based recommender, content-based filtering focuses on the characteristics of a movie rather than what other users have watched.
The basic idea is simple:
If you liked a movie, you’ll probably enjoy other movies with similar characteristics.
For example, suppose you enjoyed Inception. Since it is a science fiction thriller with complex storytelling, a content-based recommender might suggest movies like Interstellar, The Prestige, or Tenet because they share similar genres, themes, or plot elements.
To make this possible, every movie needs to be represented by its features, such as:
- Genres
- Movie overview
- Cast
- Director
- Keywords
These features are converted into numerical representations using techniques like TF-IDF (Term Frequency-Inverse Document Frequency). Once every movie is represented as a vector, we can measure how similar two movies are using Cosine Similarity.
In simple terms, the recommendation process looks like this:
- Represent each movie using its content.
- Find the movies a user has liked.
- Compare those movies with the rest of the catalog.
- Recommend the movies with the highest similarity scores.
Advantages
- Works well for new movies that don’t have many user ratings.
- Generates personalized recommendations based on a user’s interests.
- Doesn’t rely on other users’ behavior.
Limitations
Because recommendations are based only on content, they often become too similar.
For example, if you frequently watch superhero movies, the system may continue recommending superhero movies and rarely introduce you to something completely different. This is known as overspecialization, where the recommender becomes too focused on a user’s existing preferences instead of helping them discover new interests.
Collaborative Filtering: Learning from User Behavior
While content-based filtering looks at what a movie is, collaborative filtering looks at how people interact with movies. Instead of analyzing genres or plots, it learns from patterns in user behavior.
The main idea is:
People with similar preferences tend to enjoy similar movies.
Imagine two users who have both highly rated The Dark Knight, Inception, and Interstellar. If one of them also enjoys The Prestige, there’s a good chance the other user will like it too.
Unlike content-based filtering, collaborative filtering doesn’t need to know anything about the movies themselves. It only learns from the interactions between users and items.
There are three common approaches.
1. User-Based Collaborative Filtering
This approach finds users with similar tastes and recommends movies they enjoyed.
For example:
- Alice and Bob both like Inception, The Matrix, and Interstellar.
- Bob also likes Arrival.
- Since their preferences are similar, the system recommends Arrival to Alice.
The similarity between users is commonly calculated using Cosine Similarity.
Advantages
- Easy to understand and implement.
- Produces personalized recommendations.
Limitations
- Doesn’t scale well for very large datasets.
- Performance decreases when user interaction data is sparse.
2. Item-Based Collaborative Filtering
Instead of comparing users, this approach compares movies.
If many users who watched The Dark Knight also watched Batman Begins, the system learns that these two movies are related.
When someone enjoys The Dark Knight, the recommender suggests Batman Begins because similar users often watched both movies.
This is the same idea behind features like:
“Because you watched…”
Item-based collaborative filtering is generally more stable because the relationships between movies change much more slowly than user preferences.
3. Matrix Factorization
Matrix factorization is one of the most powerful collaborative filtering techniques and forms the foundation of many modern recommendation systems. Rather than directly comparing users or movies, it learns hidden patterns from the user–item interaction matrix.
Imagine every movie can be described by several invisible characteristics, such as:
- Action intensity
- Comedy level
- Romance
- Emotional depth
- Family friendliness
Similarly, every user has a preference for each of these characteristics. The interesting part is that we never manually define these features.
Instead, the algorithm automatically discovers them by analyzing millions of user interactions. These hidden characteristics are called latent factors.
Once both users and movies are represented in this shared latent space, predicting whether a user will enjoy a movie becomes much simpler. The model estimates how well a user’s preferences align with a movie’s latent features, producing a recommendation score.
Matrix factorization is particularly effective because it can uncover relationships that aren’t obvious from movie metadata alone, making it one of the key building blocks of modern recommendation systems including the hybrid model used in this project.
Implicit Feedback: Learning Without Star Ratings
Many recommendation system tutorials assume that users rate every movie they watch using a scale like 1 to 5 stars.
In reality, that rarely happens.
Think about the apps you use every day. On platforms like Netflix, YouTube, or Spotify, most users don’t rate every movie or song they consume. Instead, they leave behind behavioral signals, such as:
- Watching a movie
- Clicking on a recommendation
- Liking a movie
- Adding it to a watchlist
- Watching it multiple times
These actions are called implicit feedback because they indirectly tell us what a user might like. Unlike explicit ratings, implicit feedback introduces two important challenges.
1. No Interaction Doesn’t Mean Dislike
Suppose a user has never watched Interstellar.
Does that mean they dislike it?
Not necessarily.
They may have never seen it recommended, or simply haven’t had the chance to watch it yet. This means we can’t treat missing interactions as negative feedback. Instead, we only know that some interactions happened, while most others remain unknown.
2. Not Every Interaction Has the Same Meaning
Different user actions express different levels of interest.
For example:
- Viewing a movie page shows a small amount of interest.
- Clicking on a recommendation is a stronger signal.
- Watching the movie is even stronger.
- Liking the movie provides the strongest positive signal.
Because of this, interactions are usually assigned different weights before training the recommendation model.
Alternating Least Squares (ALS)
To learn from implicit feedback, I used Alternating Least Squares (ALS), one of the most widely used algorithms for collaborative filtering with implicit data.
Instead of predicting exact ratings like 4.5 stars, ALS focuses on a more practical question:
Which movies is this user most likely to enjoy?
This makes ALS particularly well suited for real-world recommendation systems where explicit ratings are limited or unavailable.
The algorithm works by learning two sets of hidden representations, also known as latent factors:
- A latent vector for every user that captures their preferences.
- A latent vector for every movie that captures its characteristics.
During training, ALS repeatedly updates these user and movie vectors.
It first keeps the movie representations fixed and calculates the best user representations. Then it keeps the user representations fixed and updates the movie representations. This alternating optimization process continues until the model converges, which is why the algorithm is called Alternating Least Squares.
Another important idea behind ALS is confidence weighting.
Instead of treating every interaction equally, the algorithm assigns higher confidence to stronger signals. For example, a movie that a user liked or watched multiple times provides much stronger evidence than a movie they only clicked on once.
This allows the model to learn not only what users interacted with, but also how confident it should be in those interactions.
The biggest advantage of ALS is that it is designed for ranking rather than rating prediction.
Instead of trying to estimate an exact star rating, it learns to rank the movies a user is most likely to enjoy higher than the ones they are unlikely to watch.
Hybrid Recommendation: Combining the Best of Both Worlds
By now, we’ve seen that both recommendation approaches have their own strengths and weaknesses.
Content-based filtering works well even for new movies because it relies on movie features like genres, keywords, and descriptions. However, it tends to recommend movies that are very similar to what a user has already watched, making it less effective at helping users discover something new.
On the other hand, collaborative filtering learns from the behavior of thousands of users. It can uncover interesting patterns and recommend movies that may seem unrelated but are enjoyed by people with similar tastes. The downside is that it needs enough user interaction data to make good recommendations, making it vulnerable to the cold-start problem.
Instead of choosing one approach over the other, a hybrid recommendation system combines both.
The idea is simple:
Use each model where it performs best and let them complement each other’s weaknesses.
There are several ways to build a hybrid recommender:
- Weighted Hybrid — Combine the scores from multiple models using predefined weights.
- Switching Hybrid — Choose a recommendation strategy based on the available user data. For example, use content-based filtering for new users and collaborative filtering for active users.
- Cascade Hybrid — One model generates candidate recommendations, and another model re-ranks them to produce the final list.
In CineMatch, I combined these ideas in two places. In the offline experiments, I built a weighted hybrid that blends Implicit ALS scores with content-based scores — this is the model that achieved the best evaluation results. In the live application, the system works as a switching hybrid: it picks the most personalized strategy a user’s history can support, moving from matrix factorization for active users down to content-based recommendations, genre preferences, or the popularity baseline for new users. This layered approach allows the system to provide meaningful recommendations at every stage of a user’s journey.
The Cold-Start Problem: Recommending Movies to New Users
One of the biggest challenges in any recommendation system is the cold-start problem.
Imagine a user opens your application for the very first time. They haven’t watched any movies, liked anything, or interacted with the system yet.
So, how can you recommend something personalized?
The short answer is: you can’t, not immediately.
This is why real-world recommendation systems don’t depend on a single strategy. Instead, they use a fallback approach, where the recommendation method changes depending on how much information is available about the user.
In CineMatch, I implemented this as a recommendation waterfall:
- Personalized Recommendations (Matrix Factorization) — If the user has enough interaction history, the system computes a personalized taste vector using matrix factorization and ranks the entire catalog with it.
- Content-Based Recommendations — If the user has liked only a few movies, the system builds a simple content profile and recommends similar movies.
- Genre-Based Recommendations — If the user has selected their favorite genres during onboarding but hasn’t interacted with any movies yet, recommendations are generated from those genre preferences.
- Popularity-Based Recommendations — If no user information is available at all, the system recommends the most popular and highly rated movies.
This gradual fallback strategy ensures that every user receives meaningful recommendations, even during their first visit. As users interact with more movies, the system automatically moves from general recommendations to increasingly personalized ones.
The Dataset
A recommendation system is only as good as the data it learns from. Since I wanted to build a recommender that worked with real-world data, I used The Movies Dataset from Kaggle, created by Rounak Banik. It combines the popular MovieLens ratings dataset with rich movie metadata from TMDB (The Movie Database).

The dataset contains millions of user ratings along with detailed information about each movie, making it ideal for experimenting with both collaborative filtering and content-based recommendation techniques. After cleaning, deduplication, and merging, the final catalog contained 43,549 movies with complete TMDB metadata coverage.
The Machine Learning Pipeline
Building a recommendation system isn’t just about training a model. It involves preparing the data, engineering features, training multiple models, evaluating them fairly, and finally serving them efficiently.
The overall pipeline looks like this:
Raw Data → Data Cleaning → Feature Engineering → Train/Test Split
→ Model Training → Model Evaluation → Export Artifacts
→ Recommendation API
1. Exploratory Data Analysis (EDA)
Before building any models, I explored the dataset to understand its characteristics.
This step helped answer important questions such as:
- How are ratings distributed?
- How active are users?
- How sparse is the user–item matrix?

Ratings cluster between 3 and 4 stars.
One of the biggest findings was that the interaction matrix was extremely sparse. Although there were over 26 million ratings, they covered only a tiny fraction of all possible user–movie combinations roughly 99.8% of the matrix is empty. This sparsity strongly influenced which recommendation algorithms would perform well.
2. Data Cleaning and Preprocessing
Next, I cleaned and merged the different dataset files.
This involved:
- Parsing nested JSON-like fields
- Removing corrupted records
- Handling missing values
- Merging MovieLens and TMDB datasets
- Creating a clean dataset for training
A reliable preprocessing pipeline is essential because even small data quality issues can affect every recommendation model built on top of it.
3. Train-Test Split
To evaluate the models fairly, I used a user-based 80/20 train-test split.
For each user:
- 80% of interactions were used for training.
- 20% were reserved for testing.
Splitting per user (instead of randomly across all ratings) guarantees that every test user also exists in the training data essential when the goal is to evaluate personalization. It also ensures that every recommendation model is evaluated on interactions it has never seen before, preventing data leakage and providing a realistic measure of performance.
4. Feature Engineering
The next step was preparing data for different recommendation models.
For the content-based model, I created a feature soup by combining genres, overview, keywords, director, cast, and title before generating TF-IDF vectors.

Drama and Comedy dominate the catalog. Genres are a strong signal of what a movie is, but genres alone can’t tell thousands of dramas apart, which is why the feature soup also includes overview, cast, director, and keywords.
For collaborative filtering, user interactions were converted into a sparse user–item interaction matrix that could be efficiently used during model training.
5. Model Training
With the data prepared, I trained multiple recommendation models, including:
- Bayesian Popularity Baseline
- User-Based Collaborative Filtering
- Item-Based Collaborative Filtering
- TF-IDF Content-Based Filtering
- Truncated SVD
- Implicit ALS
- Hybrid Recommendation Model
Each model represents a different recommendation strategy, allowing me to compare their strengths and weaknesses under the same evaluation setup.
6. Model Evaluation
Rather than assuming a model was better because it was more complex, I evaluated every model using the same testing procedure.
The evaluation focused on ranking metrics such as:
- Precision@10
- Recall@10
- Hit Rate
- F1-score
Using the same evaluation criteria ensured a fair comparison between all recommendation approaches.
7. Exporting Models for Inference
Training recommendation models can take several minutes, but generating recommendations for users should take only a few milliseconds.
To achieve this, the trained models and supporting artifacts were exported after training. When the FastAPI application starts, it loads these artifacts into memory, allowing recommendations to be generated quickly without retraining the models.
This separation between offline training and online inference is a common practice in production machine learning systems, making the recommendation engine both efficient and scalable.
Backend Architecture: Serving the Recommendation Models
Training a recommendation model is only half the job. The other half is making it available so users can receive personalized recommendations in real time.
For this project, I built the backend using FastAPI, with SQLAlchemy for database operations and SQLite for storing user information and interaction history. The backend is organized into separate layers so that the recommendation logic remains independent of the API.
The architecture consists of three main parts:
- Routes — Handle incoming API requests, validate user input, and return responses.
- Services — Contain the core recommendation logic, user interaction processing, and external services like poster fetching.
- Database — Stores users, their preferences, and interaction history, which are later used to generate personalized recommendations.
Loading the Models
Training machine learning models is a time-consuming process, so the models are not retrained every time a user requests recommendations.
Instead, after training, all model artifacts such as the TF-IDF matrix, matrix factorization components, and popularity scores are saved to disk.
When the FastAPI application starts, these artifacts are loaded into memory once and reused for every request. This keeps the recommendation process fast while avoiding unnecessary computation.
How a Recommendation Request Works
When a user requests recommendations, the backend follows a simple workflow:
- Retrieve the user’s interaction history and preferences from the database.
- Determine which recommendation strategy is most suitable based on the available data.
- Generate recommendation scores using the selected model.
- Remove movies the user has already interacted with.
- Return the highest-ranked movies along with their metadata, such as titles, genres, and posters.
Why This Architecture?
One important design decision was separating offline model training from online inference.
- Offline: Train the recommendation models and export the learned artifacts.
- Online: Load those artifacts once and use them to generate recommendations within milliseconds.
This separation makes the system efficient, scalable, and much closer to how recommendation systems are deployed in real-world applications.
Backend Code Organization
To keep the project modular and easy to maintain, I separated the machine learning code from the API layer. This makes it easier to experiment with models without affecting the backend and allows the same ML code to be reused for training, evaluation, and inference.
Here’s the overall project structure:
project/
├── src/
│ ├── data/ # Data loading and preprocessing
│ ├── models/ # Recommendation algorithms
│ └── evaluation/ # Evaluation metrics
│
├── scripts/
│ ├── run_preprocessing.py
│ ├── train_and_export.py
│ └── evaluate.py
│
├── api/
│ ├── routes/ # API endpoints
│ ├── services/ # Recommendation engine
│ └── models/ # Database models
│
├── models/artifacts/ # Saved ML models
├── notebooks/ # Experiments and research
└── reports/ # Evaluation results
The project is organized into three main layers:
**src/** contains all the machine learning code, including data preprocessing, recommendation algorithms, and evaluation metrics. These modules are independent of the web framework, making them reusable for both experimentation and production.**scripts/** automate the complete ML pipeline from preprocessing and training to evaluation and exporting the trained model artifacts.**api/** exposes the trained models through FastAPI. Instead of training models during a request, it simply loads the exported artifacts and performs fast inference to generate recommendations.
This separation between training, evaluation, and serving made the project easier to maintain and reflects how many production machine learning systems are designed.
Recommendation Engine Deep Dive
Everything we’ve discussed so far data preprocessing, feature engineering, and model training comes together here. When a user requests recommendations, the backend follows a series of steps to generate personalized results.
Let’s walk through that process.
Step 1: Converting User Interactions into Implicit Feedback
Unlike platforms where users rate every movie, CineMatch learns from user behavior.
Every interaction provides a different level of confidence about a user’s interest.

If a user performs multiple actions on the same movie, only the strongest interaction is kept. For example, if a user first clicks a movie and later likes it, the Like interaction becomes the final signal.
These values aren’t learned automatically they’re heuristic weights chosen to reflect the relative importance of different user actions.
The goal is simple: convert user behavior into numerical signals that recommendation models can learn from.
Step 2: Learning User Preferences with Implicit ALS
Once the interaction matrix is created, the collaborative filtering model is trained using Implicit ALS, exactly as described earlier: the algorithm alternates between updating user vectors and movie vectors until it converges, giving more confidence to stronger interactions along the way.
The result of training is a set of latent vectors one for every user and one for every movie that capture hidden patterns in preferences which aren’t explicitly defined anywhere in the data.
Step 3: Building a Content Profile
Collaborative filtering works well once enough user interactions are available, but newer users often don’t have much history.
To address this, CineMatch also builds a content profile using the movies a user has liked.
Each movie is represented by a TF-IDF vector created from its genres, overview, keywords, cast, director, and title. The user’s profile is then created by combining the vectors of their liked movies.
Once the profile is generated, Cosine Similarity is used to measure how similar every movie is to the user’s interests.
This allows the system to recommend movies with similar themes even when collaborative filtering doesn’t have enough interaction data.
Step 4: Combining Both Models
Both recommendation approaches produce useful but different signals. In the offline experiments, I combined them using a weighted hybrid approach.
The final recommendation score is calculated as:
Final Score = 0.70 × ALS Score + 0.30 × Content-Based Score
Before combining the scores, both outputs are normalized, because ALS scores and cosine similarity values exist on different numerical scales. Without normalization, one model would dominate the final recommendation regardless of its assigned weight.
The weights were selected after multiple experiments. The evaluation showed that giving more importance to ALS while allowing the content-based model to contribute complementary information produced the best overall results.
Step 5: Personalizing for Brand-New Users with Fold-In
There’s one serving problem the trained model can’t handle on its own: the latent factors were learned from users in the training data, but a person who signs up today has no user vector at all. Retraining the whole model for every new user would be absurd.
The solution is a technique called fold-in. The movie vectors already encode what the latent dimensions mean they don’t change just because one new user showed up. So the system keeps them fixed and solves a tiny least-squares problem to find the taste vector that best explains the new user’s handful of interactions. That single small computation takes microseconds, and the result is a genuine personalized ranking over the entire catalog with zero retraining.
This is the same idea behind “instant personalization” in production recommenders, and implementing it was one of the most satisfying parts of the project.
A transparency note: the weighted hybrid (Step 4) is the model that won the offline evaluation, while the live API’s top tier serves matrix-factorization fold-in recommendations, it’s the approach whose artifacts load fastest and solve most cheaply at request time. The content-based model still serves users below the fold-in threshold, so both halves of the hybrid earn their place in production.
Step 6: Handling the Cold-Start Problem
Finally, the recommendation engine decides which strategy to use based on the amount of information available about the user.

This fallback strategy ensures that every user receives meaningful recommendations, even on their very first visit.
As users continue interacting with movies, the system automatically transitions from generic recommendations to increasingly personalized ones.
Model Evaluation
Building a recommendation model is only half the challenge. The real question is:
How do we know if the recommendations are actually good?
To answer this, I evaluated every model under exactly the same conditions. Using a consistent evaluation setup ensures that the results are fair and directly comparable.
Evaluation Setup
All recommendation models were evaluated using:
- Top-10 recommendations (K = 10)
- Relevance threshold: Rating ≥ 4.0
- The same evaluation users
- The same random seed (42)
Keeping these settings fixed prevented unfair comparisons and ensured that every model was tested under identical conditions.
Evaluation Metrics
Unlike traditional machine learning problems, recommendation systems care more about ranking than predicting exact ratings. That’s why I focused on ranking-based evaluation metrics.
- Precision@10 — Measures how many of the top 10 recommended movies were actually relevant to the user.
- Recall@10 — Measures how many relevant movies the system successfully recommended.
- F1-Score — Balances Precision and Recall into a single metric.
- Hit Rate — Measures whether at least one recommendation was relevant to the user.
- RMSE — Evaluates rating prediction accuracy and was mainly used for tuning the SVD model.
The Results


Only Implicit ALS and the Hybrid clear the popularity baseline and the Hybrid wins on every metric.
Before interpreting these numbers, one calibration note: a Precision@10 of 0.05 may look tiny, but the model has to pick 10 movies out of more than 43,000 and hit the few that a specific user rated 4.0+ in their held-out data. Random guessing scores near zero, and published benchmarks on MovieLens-style data live in this same range. Offline evaluation is also naturally pessimistic a great recommendation the user simply never rated still counts as a miss.
What the Results Tell Us
Several interesting patterns emerged from the evaluation.
The Popularity Baseline proved to be a surprisingly strong benchmark. This reinforced an important lesson: before building complex machine learning models, it’s essential to compare them against simple baselines.
The traditional User-Based and Item-Based Collaborative Filtering models performed poorly because the dataset was extremely sparse. With millions of possible user–movie combinations but relatively few interactions per user, finding meaningful neighbors became difficult.
The Content-Based models also produced lower ranking scores than the popularity baseline. However, their value wasn’t reflected only in these metrics. They play an important role in handling new users and new movies, situations where collaborative filtering cannot generate reliable recommendations.
The strongest individual model was Implicit ALS. Since it is designed to optimize ranking rather than rating prediction, it consistently produced better recommendations than the traditional collaborative filtering approaches.
Finally, the Hybrid Model achieved the best overall performance. By combining the strengths of ALS and the content-based model, it generated more accurate recommendations than either model could produce on its own. While ALS captured patterns in user behavior, the content-based model contributed additional information about movie similarity, allowing the two approaches to complement each other.
The Evaluation Bug That Almost Fooled Me
One experience from this stage taught me more than any successful result.
In my early evaluation runs, the collaborative filtering models scored close to zero, and I nearly concluded that they simply didn’t work on this dataset. The real problem was hiding in the evaluation pipeline: the CF models were trained on a sample of users, but the evaluation users were selected independently so most of the users being evaluated had never appeared in the CF training data at all. The models weren’t bad; they were being tested on complete strangers.
The fix was to select the evaluation users first, and then make sure all of them were included in the CF training data. After that, every model was answering for the same users, and the comparison finally became fair.
The lesson stuck with me: when a model scores suspiciously close to zero, audit the evaluation before blaming the model.
Challenges
Building a recommendation system wasn’t just about implementing algorithms. Along the way, I faced several practical challenges that taught me valuable lessons about machine learning and real-world data.
1. Data Sparsity
The biggest challenge was the extremely sparse user–item matrix.
Although the dataset contained over 26 million ratings, they were spread across 270,000+ users and 43,000+ movies, leaving most of the matrix empty.
This sparsity made traditional neighborhood-based collaborative filtering perform poorly because many users simply didn’t have enough overlapping movie ratings. One important takeaway was that matrix factorization methods like ALS are much better suited for sparse datasets, as they learn hidden user preferences instead of relying on direct rating overlaps.
2. The Cold-Start Problem
Another major challenge was recommending movies to users with little or no interaction history. A single recommendation strategy wasn’t enough, so I implemented the multi-stage fallback approach described earlier from fully personalized recommendations down to the popularity baseline so that every user receives meaningful recommendations, even during their first visit.
3. Working with Real-World Data
The dataset itself introduced several preprocessing challenges.
I had to parse nested JSON-like fields, handle corrupted records, merge multiple datasets using different movie identifiers, and deal with missing values.
This reinforced one of the biggest lessons of the project:
Good data is often more important than a sophisticated model.
Even the best recommendation algorithm cannot compensate for poor-quality data.
4. Ranking Matters More Than Rating Prediction
One of the most interesting findings came from comparing SVD and Implicit ALS.
Although SVD achieved a good RMSE, ALS consistently produced better recommendation quality. The reason is simple:
- SVD focuses on predicting ratings.
- ALS focuses on ranking relevant items higher.
Since users care about finding good recommendations, not predicted rating values, optimizing for ranking proved to be the better choice. This changed how I think about evaluating recommendation systems.
Frontend (Briefly)
Although this project focuses on machine learning, I built a simple frontend so I could interact with the recommendation engine in real time instead of testing it through API calls.
The application is built with React, Vite, and Tailwind CSS. It provides a clean interface where users can:
- Browse personalized recommendation lists
- Search for movies
- View movie details
- Like movies and build their recommendation profile
- Select favorite genres during onboarding
Rather than handling complex business logic, the frontend acts as a bridge between the user and the recommendation engine. Every interaction such as viewing a movie, clicking a card, or liking a movie is sent to the backend, where it becomes implicit feedback for future recommendations.
One of my favorite parts of the project is seeing the recommendations evolve as more interactions are recorded. A new user initially receives popularity-based recommendations. After a few interactions, the system switches to content-based recommendations, and as more user history becomes available, the recommendation engine starts generating fully personalized suggestions.
Lessons Learned
- Start with a strong baseline. A simple popularity-based recommender taught me that a complex model isn’t always better. Always compare new models against a baseline.
- Trust your evaluation, but verify it. The near-zero collaborative filtering scores turned out to be a bug in my evaluation pipeline, not a failure of the models. Small mistakes in evaluation can lead to completely misleading results.
- Optimize for the right objective. Good rating predictions don’t always lead to good recommendations. Ranking metrics matter more for recommender systems.
- Data sparsity shapes everything. Sparse user–item data made traditional collaborative filtering struggle and highlighted the value of matrix factorization methods like ALS.
- Every model has a role. Content-based filtering didn’t achieve the best accuracy, but it was essential for handling cold-start users and improving the hybrid recommender.
- Production ML is more than model training. Building an end-to-end recommendation system involves preprocessing, feature engineering, evaluation, model serving, and efficient inference not just training algorithms.
- Hybrid models work best. Combining collaborative filtering and content-based filtering produced better recommendations than relying on either approach alone.
Conclusion
Building CineMatch was much more than creating a movie recommendation system. It was an opportunity to understand how modern recommendation engines actually work by implementing them step by step.
Through this project, I explored content-based filtering, collaborative filtering, matrix factorization, hybrid recommendation systems, and the importance of choosing the right evaluation metrics. Along the way, I also learned that building a successful recommendation system isn’t just about training models but it’s about working with messy data, handling cold-start users, designing a reliable evaluation pipeline, and serving recommendations efficiently.
The biggest takeaway for me was that machine learning is more than selecting an algorithm. Good data, thoughtful feature engineering, fair evaluation, and practical system design all contribute to the final result.
If you’re learning machine learning or recommendation systems, I highly recommend building one yourself. It’s one of the few projects that combines data preprocessing, feature engineering, linear algebra, model evaluation, and backend deployment into a single, practical application. You’ll make mistakes, debug unexpected results, and learn far more than you would by simply using an existing library.
There’s still plenty of room to improve this project experimenting with deep learning, embeddings, sequential recommendation models, or even real-time online learning. But building this hybrid recommender gave me a solid understanding of the core ideas behind modern recommendation systems, and that’s exactly what I set out to achieve.
Thanks for reading! If you have suggestions, questions, or ideas for improving the recommender, I’d love to hear your thoughts.
Links
Dataset: https://www.kaggle.com/datasets/rounakbanik/the-movies-dataset
GitHub Repository: https://github.com/Sachinkc263/Movie_recommendation_system
Live Demo: https://sachinkc263.github.io/Movie_recommendation_system
If this case study helped you understand recommendation systems, a clap helps more learners find it.
메타데이터
- post_id
- fb1b5adc8673
- slug
- learning-recommendation-systems-by-building-a-hybrid-movie-recommender-fb1b5adc8673
- url
- https://medium.com/@sachinkc263/learning-recommendation-systems-by-building-a-hybrid-movie-recommender-fb1b5adc8673
- canonical_url
- https://medium.com/@sachinkc263/learning-recommendation-systems-by-building-a-hybrid-movie-recommender-fb1b5adc8673
- author_url
- https://medium.com/@sachinkc263
- status
- ok
- fetched_at
- 2026-08-02 00:36:41