Quick Answer

Recommendation systems mainly use two approaches: collaborative filtering, which recommends based on what similar users liked without knowing anything about the items themselves, and content-based filtering, which recommends items similar in attributes to what a specific user already liked. Collaborative filtering gives sharper recommendations once there's enough data but fails completely for brand-new users or items — the cold-start problem — which is why real systems combine both.

Two Different Questions a Recommender Can Ask

Content-based filtering asks: “what is this item like, and what has this specific user liked before with similar attributes?” Recommend another React course because the user already finished two other frontend courses, comparing on tags, category, and difficulty level. It only needs one user's own history plus item metadata — no data from anyone else required.

Collaborative filtering asks a completely different question: “which other users behaved like this one, and what did they like that this user hasn't tried yet?” — or, flipped around, “which other items got liked by the same people who liked this one?” It needs zero understanding of what the items actually are, just a big matrix of who-liked-what.

User-Based Collaborative Filtering, With Real Numbers

I built a real 6-user, 6-course ratings matrix (0 means not rated) and computed actual cosine similarity between users, restricted to courses both rated — padding unrated courses with a similarity-distorting zero would wrongly treat “never took this course” the same as “took it and hated it”:

import numpy as np

courses = ["Python Basics", "DSA in Java", "Web Dev (React)", "SQL for Beginners", "ML Roadmap", "DevOps Intro"]
users = ["Aditi", "Rohan", "Meera", "Karan", "Zoya", "Farhan"]

R = np.array([
    #  PyB  DSA  Web  SQL  ML   DevOps
    [5,    4,   0,   3,   4,   0],   # Aditi
    [4,    5,   0,   2,   5,   0],   # Rohan
    [0,    0,   5,   4,   0,   4],   # Meera
    [1,    2,   4,   5,   0,   4],   # Karan
    [5,    5,   0,   3,   5,   0],   # Zoya
    [0,    1,   5,   3,   0,   5],   # Farhan
], dtype=float)

def cosine_sim(v1, v2):
    mask = (v1 != 0) & (v2 != 0)   # only compare on courses BOTH rated
    if not np.any(mask):
        return 0.0
    a, b = v1[mask], v2[mask]
    denom = np.linalg.norm(a) * np.linalg.norm(b)
    return float(np.dot(a, b) / denom) if denom else 0.0

Running this against Zoya as the target user produced:

cosine_sim(Zoya, Aditi)  = 0.9938
cosine_sim(Zoya, Rohan)  = 0.9911
cosine_sim(Zoya, Meera)  = 1.0000
cosine_sim(Zoya, Karan)  = 0.7131
cosine_sim(Zoya, Farhan) = 0.7593

Ranked neighbors: [('Meera', 1.0000), ('Aditi', 0.9938), ('Rohan', 0.9911),
                   ('Farhan', 0.7593), ('Karan', 0.7131)]

Meera comes out as a perfect 1.0 match to Zoya, which looks like a strong signal — until you check why. Looking at the raw matrix, Zoya and Meera only have one course in common (“SQL for Beginners”, rated 3 and 4 respectively). Cosine similarity between two single-number vectors is mathematically guaranteed to be exactly 1.0 regardless of the actual values, because the angle between any two positive scalars in one dimension is always zero. This isn't a flaw in my code — it's a real, well-known pitfall of running cosine similarity on sparse data, and it's exactly the kind of thing you only catch by actually running the numbers instead of describing the math abstractly. Real systems guard against it by requiring a minimum number of co-rated items (often 3–5+) before trusting a similarity score at all.

Turning Similarity Scores Into Actual Recommendations

Using the raw top-2 neighbors (Meera and Aditi) to predict Zoya's rating on her two unrated courses, via a similarity-weighted average of what each neighbor rated them:

Zoya's unrated courses: ['Web Dev (React)', 'DevOps Intro']

Top-2 neighbors used: [('Meera', 1.0000), ('Aditi', 0.9938)]

Predicted ratings for Zoya (weighted average of top neighbors):
  Web Dev (React): 5.000
  DevOps Intro: 4.000

Here's a real limitation the numbers themselves expose: if you instead exclude Meera as unreliable (correctly, given the single-item overlap above) and lean only on Aditi and Rohan — both genuinely well-correlated with Zoya across four shared courses — neither of them has a rating for “Web Dev (React)” or “DevOps Intro” either. The prediction denominator would be zero and no recommendation could be produced for those two courses at all. That's the actual tension real collaborative filtering systems have to manage: a thin-overlap neighbor with a suspicious 1.0 score is sometimes the only source of any signal at all, so a production system needs a rule for when to trust a low-overlap match versus when to just admit it has no recommendation to make.

Item-Based Collaborative Filtering: Flip the Matrix

Item-based CF compares columns instead of rows — similarity between two courses based on the pattern of which users rated them alike. It's often preferred in production (Amazon's original large-scale recommender was item-based) because an item's similarity profile is far more stable over time than any individual user's changing preferences.

Courses most similar to 'Web Dev (React)' (item-based cosine similarity):
  Python Basics: 1.0000
  DevOps Intro: 0.9945
  SQL for Beginners: 0.9574
  DSA in Java: 0.9080
  ML Roadmap: 0.0000

The same pitfall from step 2 shows up again, independently: “Python Basics” scores a perfect 1.0 against “Web Dev (React)”, and checking the matrix shows exactly one user (Karan) rated both. “DevOps Intro”, by contrast, scores a real 0.9945 against “Web Dev (React)” backed by three shared raters (Meera, Karan, and Farhan) — a genuinely meaningful number. Seeing the identical artifact twice, from two unrelated angles on the same dataset, is a stronger confirmation that it's a structural property of cosine similarity on sparse data, not a fluke of one example.

Cold Start: Where Collaborative Filtering Breaks Completely

Running the same similarity function against a brand-new user with no ratings at all:

New user vector: [0. 0. 0. 0. 0. 0.]
Similarity to every existing user: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

Zero, against every single existing user — not a low score, an undefined one, since there's no overlapping rated item to compute a cosine over at all. The same failure hits from the other side: a brand-new item with zero ratings can't be matched to anything under item-based CF, and no user-based neighbor can recommend it either. This is the classic cold-start problem, and it's structural, not a tuning issue — no amount of better math fixes “there is no data yet.”

How a Real System Combines Both

A practical hybrid: fall back to content-based filtering (course tags, category, difficulty, skills) for brand-new users and items where there's no interaction history, then shift weight toward collaborative filtering's signal as real interaction data accumulates — typically as a blend whose weighting moves from content-based toward collaborative as a user's rating count grows. Layer in a minimum-co-rated-items threshold before trusting any single similarity score, precisely to avoid the thin-overlap 1.0 artifacts demonstrated above.

At real scale, systems like Netflix's or Amazon's also add popularity and recency priors, and often use matrix factorization — a mathematically different way of deriving latent user and item factors — rather than plain neighbor-based CF. But the core distinction between the two families, and the cold-start asymmetry between them, holds regardless of which specific algorithm implements each side.

Frequently Asked Questions

What's the difference between collaborative filtering and content-based filtering? Collaborative filtering recommends based on what similar users liked, without knowing anything about the items; content-based filtering recommends items similar in attributes to what one specific user already liked, without needing data from other users at all.
What is the cold-start problem in recommendation systems? It's the situation where collaborative filtering has no data to work with: a brand-new user has zero rated items to compare against anyone, and a brand-new item has no ratings to be matched on. I confirmed this directly — a new user's all-zero rating vector produced a cosine similarity of exactly 0.0 against every real user in my dataset.
Is item-based or user-based collaborative filtering more reliable in practice? Item-based tends to be more stable in production because an item's similarity profile changes far more slowly than a user's preferences do; Amazon's original large-scale recommender was item-based for exactly this reason.
Why did two unrelated items in your example get a 'perfect' 1.0 similarity score? Because cosine similarity computed over sparse data becomes mathematically trivial when only one user rated both items — the cosine of any two single positive numbers is always exactly 1.0 regardless of their actual values. This happened twice independently in my test data, confirming it's a structural pitfall, not a coincidence.
How do real recommendation systems avoid the thin-overlap similarity problem? By requiring a minimum number of co-rated items before trusting a similarity score, applying shrinkage that discounts scores based on very little overlapping data, and blending in content-based signals or popularity priors rather than relying purely on raw cosine similarity.
Do real systems like Netflix just use collaborative filtering? No. Production recommenders typically blend collaborative filtering with content-based signals, popularity and recency priors, and often matrix factorization at scale, specifically to smooth over the cold-start and thin-overlap problems plain neighbor-based CF has on its own.