Back to Articles
Machine LearningRecommendationsNLP

TF-IDF + FAISS: Building a Fast Job Recommendation System

June 15, 20265 min read

Why Not Just Use Cosine Similarity?

Brute-force cosine similarity is O(nยทd) per query โ€” fine for 1,000 items, painful for 100,000. FAISS (Facebook AI Similarity Search) uses approximate nearest-neighbor algorithms to achieve sub-millisecond search at scale.

Building the TF-IDF Index

from sklearn.feature_extraction.text import TfidfVectorizer
import faiss
import numpy as np

def build_job_corpus(jobs: list[dict]) -> list[str]:
    return [
        f"{j['title']} {j['description']} {' '.join(j['required_skills'])}"
        for j in jobs
    ]

corpus = build_job_corpus(jobs)
vectorizer = TfidfVectorizer(max_features=5000, stop_words="english")
tfidf_matrix = vectorizer.fit_transform(corpus).toarray().astype("float32")

# L2-normalize for cosine similarity via inner product
faiss.normalize_L2(tfidf_matrix)
index = faiss.IndexFlatIP(tfidf_matrix.shape[1])
index.add(tfidf_matrix)

Querying for Recommendations

def recommend(user_profile: str, k: int = 10) -> list[int]:
    query_vec = vectorizer.transform([user_profile]).toarray().astype("float32")
    faiss.normalize_L2(query_vec)
    scores, indices = index.search(query_vec, k)
    return indices[0].tolist()

The user profile is constructed from:

  • Job titles from interaction history (apply, save, view)
  • Self-reported skills
  • Location preferences

Proximity Sorting

For the "sort by proximity" feature, I combined the FAISS similarity score with a geographic distance score:

combined_score = alpha * relevance_score + (1 - alpha) * proximity_score

with alpha=0.7 as the default (relevance-first).

Performance Results

| Method | 10k jobs | 100k jobs | |--------|----------|-----------| | Brute-force cosine | 18ms | 182ms | | FAISS IVFFlat | 1.2ms | 2.1ms |

~90x speedup at 100k scale with <1% accuracy loss.