Today's primary content: Saturday Deep Dive โ Design a Recommendation System (sent above).
Mini Quiz: Days 41-44
Q1: [System Design] What are the two main components of a distributed task queue like Celery, and what role does each play?
A1: A message broker (e.g., Redis or RabbitMQ) holds the task queue and delivers tasks to workers; worker processes pull tasks, execute them, and report results. The broker decouples task producers from consumers, enabling horizontal scaling of workers independently.
Q2: [Algorithms] In Floyd's Cycle Detection (used in Linked List Cycle), why does the fast pointer always catch the slow pointer inside the cycle?
A2: Once both pointers are inside the cycle, the fast pointer gains 1 step per iteration on the slow pointer. The gap closes by 1 each step, so it's guaranteed to reach 0 (meet) within cycle_length iterations โ never "jumping over" the slow pointer.
Q3: [AI/ML] What is quantization in LLMs, and what is the practical tradeoff between INT8 and FP16 quantization?
A3: Quantization reduces weight precision (e.g., FP32 โ INT8) to shrink model size and speed up inference. INT8 uses 4ร less memory than FP32 with ~1-2% accuracy loss; FP16 uses 2ร less with negligible loss. INT4 (via GPTQ/AWQ) allows running 70B models on consumer GPUs but with more noticeable quality degradation on complex tasks.
๐ฌ Saturday Deep Dive: Design a Recommendation System (15 min read)
ๆจ่็ณป็ปๆฏ็ฐไปฃไบ่็ฝไบงๅ็ๆ ธๅฟๅผๆใNetflix ็"็ไฝ ๅๆฌข"ใYouTube ็้ฆ้กต่ง้ขใAmazon ็"่ดญไนฐไบ่ฟไปถๅๅ็็จๆท่ฟ่ดญไนฐไบโฆโฆ"โโ่ฟไบ่ๅ้ฝๆฏๆจ่็ณป็ปๅจๅฎๆถ่ฟ่ฝฌใๅฏนไบๅ็ซฏๅทฅ็จๅธๆฅ่ฏด๏ผๆจ่็ณป็ปๆถต็ไบ็ณป็ป่ฎพ่ฎกไธญๆ้พ็ๅ ไธช็ปดๅบฆ๏ผๆตท้ๆฐๆฎๅค็ใๅฎๆถไธ็ฆป็บฟ็ๆ่กกใๅค้ถๆฎต่ฟๆปคๆผๆใไปฅๅ ML ไธๅทฅ็จ็ๆทฑๅบฆ็ปๅใ
A recommendation system is the core engine of modern internet products. Netflix's "Top Picks," YouTube's homepage, Amazon's "Customers also bought" โ they all run on recommendation systems. For backend engineers, this topic spans the hardest dimensions of system design: massive data pipelines, real-time vs. offline tradeoffs, multi-stage filtering funnels, and the deep integration of ML and engineering.
Why it matters in interviews: This question tests whether you can break down a fuzzy product requirement into a concrete, scalable architecture. It's one of the canonical "design a ML-backed system" questions at Google, Meta, Netflix, and Amazon.
Part 1: Theory / ็่ฎบๅบ็ก (5 min)
ไธ็งๆ ธๅฟๆจ่็ฎๆณ / The Three Core Approaches
1. ๅๅ่ฟๆปค (Collaborative Filtering)
ๆ ธๅฟๆๆณ๏ผ็จๆท A ๅ็จๆท B ็ๅๅฒ่กไธบ็ธไผผ๏ผๅ A ๅๆฌข็ไธ่ฅฟ B ๅฏ่ฝไนๅๆฌขใ
Every major recommendation system uses this funnel. The key insight: each stage trades recall for precision. At the top, you want to cast a wide net; at the bottom, you apply your most expensive, most accurate model.
Part 2: Step-by-Step Implementation / ไธๆญฅไธๆญฅๅฎ็ฐ (8 min)
# two_tower_retrieval.py# The Two-Tower (Dual Encoder) model architecture used by YouTube DNN, TikTok, etc.# User tower and item tower are trained jointly but served independently.import numpy as np
from dataclasses import dataclass
from typing import List
import faiss # pip install faiss-cpu
@dataclass
class UserEmbedding:
user_id: str
vector: np.ndarray # shape: (128,)
updated_at: float # unix timestamp
@dataclass
class ItemEmbedding:
item_id: str
vector: np.ndarray # shape: (128,)class TwoTowerRetriever:
"""
Manages ANN index for fast approximate nearest neighbor retrieval.
In production: user embeddings updated every 5 min (stream processing),
item embeddings updated every 6 hours (batch job).
"""def __init__(self, embedding_dim: int = 128):
self.embedding_dim = embedding_dim
# FAISS IVF (Inverted File) index โ production choice for >10M items# IVFFlat: split vectors into n_clusters, only search relevant clusters# Tradeoff: ~10% accuracy loss vs exhaustive search, 100x faster
n_clusters = 1000# rule of thumb: sqrt(n_items) for 10M items
quantizer = faiss.IndexFlatIP(embedding_dim) # Inner product = cosine similarity (on normalized vectors)
self.index = faiss.IndexIVFFlat(quantizer, embedding_dim, n_clusters, faiss.METRIC_INNER_PRODUCT)
self.item_ids: List[str] = [] # parallel array: index position โ item_id
self.is_trained = Falsedef build_index(self, item_embeddings: List[ItemEmbedding]):
"""
Offline batch job: (re)build the ANN index.
Called every 6 hours, swapped atomically (blue-green pattern).
"""
vectors = np.array([e.vector for e in item_embeddings], dtype='float32')
# Normalize to unit vectors โ converts cosine similarity to inner product
faiss.normalize_L2(vectors)
# Train the IVF quantizer (learns cluster centroids via k-means)# Requires at least 30 * n_clusters training samples
self.index.train(vectors)
self.index.add(vectors)
self.item_ids = [e.item_id for e in item_embeddings]
self.is_trained = True# Production note: after building, serialize index to S3# faiss.write_index(self.index, '/tmp/item_index.faiss')def retrieve(self, user_embedding: UserEmbedding, top_k: int = 500) -> List[str]:
"""
Online serving: retrieve top_k candidates for a given user.
Target latency: <5ms for 10M items.
"""ifnot self.is_trained:
raise RuntimeError("Index not built. Run build_index() first.")
query = user_embedding.vector.reshape(1, -1).astype('float32')
faiss.normalize_L2(query) # same normalization as at index time# nprobe: how many clusters to search. Higher = more accurate, slower.# Production: nprobe=50 gives ~95% recall@500 with <5ms latency
self.index.nprobe = 50
distances, indices = self.index.search(query, top_k)
# Convert flat indices back to item IDs; filter -1 (empty slots)return [
self.item_ids[idx]
for idx in indices[0]
if idx != -1
]
class RankingModel:
"""
Simplified ranking model interface.
In production: DLRM (Deep Learning Recommendation Model) or
a gradient boosted tree (XGBoost/LightGBM) for pre-ranking.
"""def score(self, user_features: dict, item_features: dict) -> float:
"""
Returns P(engagement | user, item).
Features include:
- User: embedding, historical CTR, session context, demographic
- Item: embedding, global CTR, freshness, category, creator follower count
- Cross features: user ร item category match, historical interactions
"""# Placeholder โ real model would call a TF Serving / TorchServe endpoint# or run a local ONNX model for sub-millisecond latencyraise NotImplementedError("Load your model weights here")
class RecommendationPipeline:
"""
Orchestrates the full retrieval + ranking funnel.
"""def __init__(self, retriever: TwoTowerRetriever, ranker: RankingModel):
self.retriever = retriever
self.ranker = ranker
def get_recommendations(
self,
user_embedding: UserEmbedding,
user_features: dict,
item_features_store: dict, # item_id โ features
n_final: int = 20
) -> List[str]:
# Stage 1: Candidate Generation (~1000 items)
candidates = self.retriever.retrieve(user_embedding, top_k=500)
# (In practice, merge with CF-based and trending pools here)# Stage 2: Ranking (score all candidates)
scored = []
for item_id in candidates:
if item_id notin item_features_store:
continue# item not in catalog (e.g., deleted)
score = self.ranker.score(user_features, item_features_store[item_id])
scored.append((item_id, score))
# Sort by score descending
scored.sort(key=lambda x: x[1], reverse=True)
# Stage 3: Re-ranking (diversity injection)
final = self._inject_diversity(scored, n_final)
return final
def _inject_diversity(self, scored: List[tuple], n: int) -> List[str]:
"""
Ensure we don't show all items from the same creator/category.
Simple approach: cap same-creator items to 2 in the final slate.
"""# In production: use MMR (Maximal Marginal Relevance) or DPP (Determinantal Point Process)
result = []
seen_count = {} # creator_id โ countfor item_id, score in scored:
if len(result) >= n:
break# placeholder diversity check
result.append(item_id)
return result
็นๅพๅทฅ็จ๏ผๅฎๆถ vs ็ฆป็บฟ / Feature Engineering: Real-Time vs Offline
# feature_store_client.py# Production systems use a feature store (Feast, Tecton, or custom) # to serve features with consistent definitions between training and serving.import redis
import json
from typing import Dict, Any, Optional
class FeatureStoreClient:
"""
Unified interface for online (Redis) and offline (S3/Hive) features.
Key insight: Training/serving skew is the #1 cause of recommendation
quality degradation. A feature store ensures the same feature computation
logic is used at both training time and serving time.
"""def __init__(self, redis_client: redis.Redis):
self.redis = redis_client
def get_user_features(self, user_id: str) -> Optional[Dict[str, Any]]:
"""
Fetch real-time user features from Redis.
What's stored:
- user_embedding: 128-dim float vector (updated every 5 min by Flink)
- recent_categories: top-3 categories clicked in last hour (stream)
- session_count_today: integer counter (atomic increment)
- historical_ctr: click-through rate across all sessions (batch, updated hourly)
"""
raw = self.redis.get(f"user_features:{user_id}")
if raw isNone:
# Cold start: new user, return default featuresreturn self._default_user_features()
return json.loads(raw)
def get_item_features(self, item_id: str) -> Optional[Dict[str, Any]]:
"""
Fetch item features.
What's stored:
- item_embedding: 128-dim float vector (updated every 6h by batch job)
- global_ctr_7d: click-through rate over last 7 days (batch)
- freshness_score: decay function of item age (recomputed every 15 min)
- creator_id: string (static)
- category: enum (static)
"""
raw = self.redis.get(f"item_features:{item_id}")
if raw isNone:
returnNone# item not in catalogreturn json.loads(raw)
def _default_user_features(self) -> Dict[str, Any]:
"""
For new users: use global popular items (popularity-based fallback).
This is the standard cold-start solution.
"""return {
"user_embedding": [0.0] * 128, # zero vector โ will match popular items"recent_categories": [],
"session_count_today": 0,
"historical_ctr": 0.05# global average CTR
}
def update_user_embedding(self, user_id: str, new_embedding: list, ttl_seconds: int = 86400):
"""
Called by Flink stream job after processing user events.
TTL ensures stale embeddings don't persist indefinitely.
"""
current = self.get_user_features(user_id) or {}
current["user_embedding"] = new_embedding
self.redis.setex(
f"user_features:{user_id}",
ttl_seconds,
json.dumps(current)
)
Part 5: Interview Simulation / ้ข่ฏๆจกๆ (3 min)
Q1: How would you handle the cold start problem for new users?
A: Two-pronged approach. Short-term: show globally popular items, use any available signals (device type, geo, time of day). Long-term: onboarding flow where user explicitly declares interests; these explicit signals are extremely high-value. Once the user has 5-10 interactions, switch to personalized recommendations.
Q2: How do you ensure the recommendation system doesn't create a filter bubble?
A: Enforce diversity constraints at the re-ranking stage. Two techniques: (1) Determinantal Point Process (DPP) โ mathematically maximizes diversity while preserving relevance; (2) ฮต-greedy exploration โ dedicate 10-15% of recommendation slots to "exploration" (novel categories, emerging creators). Also track diversity metrics (intra-list diversity, catalog coverage) in your A/B test dashboards.
Q3: The ranking model starts degrading after a few months. What do you do?
A: Likely causes: (1) distribution shift โ user behavior changed, model trained on stale data; (2) feature drift โ upstream feature pipeline changed but model wasn't retrained. Solution: implement continuous training (retrain model weekly/daily on fresh data). Monitor feature distributions with alerts on statistical drift (KL divergence). Use a Champion/Challenger framework to always have a fresh challenger model ready.
Q4: How would you design the system to handle 10M concurrent users at peak?
A: The key is pre-computation. You can't run full retrieval + ranking in real-time for everyone. Strategies: (1) Pre-compute recommendations for your top 5% most active users every 5 minutes (they drive 80% of traffic); (2) Cache retrieval candidates โ user embeddings change slowly, so cache the ANN search results for 5-10 minutes; (3) Stateless ranking servers behind a load balancer โ ranking is CPU/GPU bound and scales horizontally; (4) Feature hydration via Redis with local read replicas near each ranking server.
Q5: How do you measure if your recommendation system is actually good?
A: Never rely on a single metric. Offline: precision@k, recall@k, nDCG (normalized discounted cumulative gain), AUC on held-out test set. Online: CTR, watch time per session, D7/D30 retention. Business: revenue per user, subscriber growth. Crucially, watch for Goodhart's Law โ optimizing CTR alone leads to clickbait. Use a balanced scorecard. Also track long-term metrics like catalog coverage and diversity to avoid the filter bubble trap.
Next Saturday Deep Dive: Coming up in the Trees / DP territory โ Merge K Sorted Lists or advanced tree traversal patterns.