Recommendation System Interview Questions | JiQuest

add

#

Recommendation System

System design deep dive · HLD

Design a Recommendation System (Amazon-style "customers also bought"): full high-level design.

Requirements, back-of-envelope capacity estimation, a high-level design diagram, a deeper deployment architecture diagram, sequence diagrams for offline training and online serving, and an entity-relationship diagram for the data model - with the reasoning an interviewer expects behind every box and arrow.

300MActive customers
350MCatalog items indexed
<100msp99 serving budget
Product pageviewing item X Rec serviceorchestrates Rec cachehit ~95% Ranked listtop-20 items ANN + rankeron cache miss back to page, <100ms

1. Clarify requirements before drawing any box

A strong system design answer starts by pinning down scope out loud. For a recommendation system, that means separating "what gets recommended and how" from "how well it must perform," and stating the scale assumptions (catalog size, event volume) that every later diagram depends on.

Functional requirements

Item-to-item recommendationsGiven an item being viewed, return "customers who bought this also bought" - co-purchase/co-view similarity.
Personalized recommendationsGiven a user, return a ranked "recommended for you" list built from their own interaction history.
Continuous feedback loopClicks, purchases, and add-to-cart events must feed back so future recommendations improve.
Latency-bounded ranked outputReturn a top-N ranked list within the page's request latency budget, not as a background job.

Non-functional requirements

Low latency<100ms p99 including candidate generation + ranking, on the critical path of a page load.
Massive catalog scaleHundreds of millions of items - brute-force scoring every item per request is infeasible.
Batch-scale trainingBillions of interaction events/day; offline training must be distributed batch, not synchronous.
Graceful cold startFalls back to non-personalized, globally popular items for brand-new users or items.
Explicitly out of scope Full search/query-based product discovery (that's a separate search-relevance system), real-time streaming model retraining (batch retraining is the default here), and designing the internals of the ranking model architecture itself (we assume a standard learned ranker - a two-tower network or gradient-boosted tree - and focus on the system around it).

2. Back-of-the-envelope capacity estimation

These numbers decide almost everything downstream: whether brute-force scoring is even conceivable, how big the vector index gets, how much Kafka throughput the feedback loop needs, and how large the serving cache must be.

MetricAssumptionResulting estimate
Recommendation requests500M page views/day need a rec slot500M ÷ 86,400s ≈ 5,800 req/s average, ≈17,000 req/s peak (×3)
Item embedding index size350M items × 128-dim float32 (4B/dim)350M × 128 × 4B ≈ 179 GB raw vectors; ≈270-360 GB once the HNSW graph overhead is added
Interaction events (feedback loop)Clicks + purchases + add-to-cart, ≈5B events/day5B ÷ 86,400s ≈ 58,000 events/s average; Kafka sized for ≈150,000 events/s peak
Offline training dataset / runTrailing 60-day window of interaction logs≈300B raw events, downsampled/negative-sampled to ≈20-30B training rows (≈5-10 TB compressed Parquet)
Online rec cache sizeTop-50 cached items/user × 300M users≈1 KB/user (ids + scores) × 300M ≈ 300 GB across a sharded Redis cluster
Why this matters The 350M-item catalog is the number that rules out scoring the whole catalog per request - even at 1µs per item that's 350ms of pure compute, blowing the entire latency budget. That single constraint is what forces the two-stage candidate-generation-then-ranking design in every section below, the same way the billions-of-events/day number rules out synchronous online training.

3. High-level design (HLD)

The HLD names the major components and the one-directional data flow between them - the online serving path that answers a request, and the offline/feedback loop that keeps embeddings and the ranker up to date - without committing yet to replica counts or regions.

Clientproduct page API gatewayauth, routing Rec serviceorchestrates fan-out Rec cachechecked first, ~95% hit Candidate genANN over 350M items Ranking servicescores ~500 candidates Embedding storeFAISS / HNSW index Feature storeonline, low-latency Event ingestionclicks/purchases → Kafka Offline trainingbatch job, nightly
Stateless orchestrationLatency-critical servingDurable vector/feature storageAsync / batch

What each box owns

Rec service (orchestrator)

Receives the request (a user id and/or an item id), checks the rec cache first, and on a miss fans out to candidate generation and then ranking. It owns the overall latency budget and is what enforces "return a decent list within the deadline, even if a downstream step is slow" via short timeouts and fallback to popularity-based results.

Candidate generation (ANN over item embeddings)

Takes the query item's or user's embedding vector and runs an approximate nearest-neighbor search over the embedding store to narrow 350 million items down to a few hundred plausible candidates in single-digit milliseconds. It deliberately does not try to be precise - recall of "the true top-K is probably in here somewhere" is enough, because the ranking service will re-score this small set precisely.

Ranking service (learned ranker)

Scores the few hundred candidates with a model that can afford to be expensive per-item (a two-tower network or gradient-boosted tree) because it only runs on candidates, not the whole catalog. It pulls user, item, and context features from the online feature store at request time - things like the user's last-viewed categories and the item's live popularity score.

Feature store: online vs offline split

A Feast-style split: an offline store (data lake / warehouse) holds the full historical feature history used to build training datasets, while an online store (Redis/DynamoDB) holds only the latest value of each feature, optimized for single-digit-millisecond point lookups at request time. The same feature definitions are used in both places so training and serving never see a different computation for the "same" feature (train/serve skew).

Rec cache, event ingestion, and the offline training loop

The rec cache holds precomputed ranked lists per user/context so most requests never touch candidate generation or ranking at all. Event ingestion publishes every click, purchase, and add-to-cart to Kafka without blocking the page. The offline training pipeline runs on a schedule, retrains embeddings and the ranker from that event history, and pushes new artifacts to the embedding store and ranking service - this is the loop that makes recommendations improve over time.

4. Detailed architecture diagram

The architecture diagram takes every HLD box and answers "how is this actually deployed, and how does feedback get from a click back into a ranking decision?" - which is what an interviewer is checking for once they've accepted the high-level shape.

Edge / serving layer API gateway + L7 LB Session/context builderdevice, page type Auth / rate limiter Region: us-east-1 (primary) Rec svc ×10 pods ANN shards ×12 (HNSW) Ranking svc ×14 pods Redis rec cache (8 shards) Region: eu-west-1 (read replica) Rec svc ×4 pods Ranking svc ×4 pods ANN index (read replica)synced from model registryon each publish Offline training tier Data lake (S3) Spark/Raytraining cluster Model registry trains on trailing 60-day window; validates recall@K/NDCG before publishing Real-time feedback pipeline Kafka topic Stream processor(Flink) updates online features; affects next request, not this one Online feature store Redis / DynamoDBper-user features read by ranking svc
DecisionChoiceReasoning
Retrieval strategyTwo-stage: ANN candidate generation, then a learned rankerScoring all 350M items with a full ranking model per request is computationally infeasible inside a 100ms budget; ANN narrows to ~500 cheaply, so the expensive model only runs on that small set.
ANN index algorithmHNSW for the hot catalog tier; IVF + product quantization for the long tailHNSW gives high recall at low latency but keeps full-precision vectors in memory (~300GB); IVF+PQ compresses vectors 8-16×, letting rarely-viewed items fit in memory affordably at a small recall cost.
Retraining cadenceNightly/batch retraining, not continuous online learningBillions of daily events make synchronous online learning hard to validate and easy to regress silently; nightly batch gives a stable, offline-validated checkpoint, while the online feature store bridges the freshness gap.
Rec cache TTL15-30 minute TTL, refreshed by a background jobPrecomputed caching is what makes <100ms serving affordable at 300M-user scale; a longer TTL trades some staleness for far fewer expensive ranking calls, while recent clicks still nudge in-session re-ranking via the feature store.

5. Sequence diagrams for the two critical flows

A sequence diagram is where an interviewer checks whether you actually understand call order, what is synchronous versus fire-and-forget, and how the offline and online halves of the system connect.

5.1 Offline training and embedding pipeline (batch)

Training job Data lake Offline eval Model registry Serving nodes 1. read interaction logs (60-day window) 2. batched event stream (Parquet) 3. trainEmbeddings() + trainRanker() [distributed Spark/Ray job] 4. evaluate candidate model 5. recall@50 / NDCG@10 metrics 6. push embeddings + ranker artifact v482 7. ack, version registered 8. notify: new version available 9. pull artifact, load as shadow/canary (5% traffic)

Steps 4-5 are the guardrail: a candidate model that doesn't beat the current production model on an offline metric never reaches step 6. Steps 8-9 are drawn dashed because the rollout to serving nodes is a controlled canary, not an instant global swap - only after the canary's live guardrail metrics (click-through, revenue-per-session) hold up does traffic ramp from 5% to 100%.

5.2 Online request for recommendations, plus the async feedback event

Client Rec service Candidate gen Ranking Cache Kafka 1. GET /recommendations?item=X (page load) 2. ANN lookup(item/user vec, k=500) 3. 500 candidate item_ids 4. score(candidates, user+item+context features) 5. ranked top-20 with scores 6. SET cache_key → ranked list (TTL 15m) 7. 200 OK {recommendations} 8. click on a rec → event published (fire-and-forget, async)

On a cache hit, steps 2-6 collapse entirely and the response returns from the cache alone in a few milliseconds - the common case for a returning user's homepage. Step 8 is drawn dashed and gray for the same reason the URL shortener's click log is: it is published and never awaited, so a slow or backed-up stream processor can never add latency to the page. A separate stream processor (shown in the architecture diagram) consumes that event and updates the online feature store, which can influence the *next* request's ranking or an in-session re-rank, but never this one.

6. Entity-relationship (ER) diagram and data model

The data model has to answer three questions: where do per-user features live so the ranker can read them in a millisecond, how are item embeddings stored so an ANN index can serve them, and how is a cached recommendation list keyed so the right list comes back for the right context.

user_features PK user_id BIGINT recent_categories ARRAY price_affinity FLOAT last_active TIMESTAMP updated_at TIMESTAMP updated incrementally by the stream processor item_embeddings PK item_id BIGINT embedding VECTOR(128) category VARCHAR popularity_score FLOAT updated_at TIMESTAMP updated by the offline training job, looked up by item_id - no FK, pure KV recommendation_cache PK cache_key VARCHAR FK user_id BIGINT ranked_items ARRAY<id> generated_at TIMESTAMP ttl_seconds INT cache_key = user_id + context 1N one user's feature row is conceptually referenced by many cached entries (one per context: homepage, product page, email) item_embeddings has no FK at all - the candidate-gen service looks it up by item_id as a pure vector/key-value read, never joined

Key modeling decisions

cache_key includes context, not just user_idThe homepage list and a "similar to this item" list for the same user are different cached entries, refreshed on different triggers.
embedding is a flat vector blob, not columns128 float32 values are stored and indexed as one opaque vector; no query ever needs to filter on an individual dimension.
popularity_score is denormalized onto item_embeddingsIt's the cold-start fallback signal, so it must be readable in the same lookup as the embedding itself, with no extra join.
user_features updates are incremental mergesThe stream processor patches fields like recent_categories rather than rewriting the whole row on every event, keeping write amplification low.
Storage choiceUse whenWatch out for
Relational (Postgres), user_featuresYou need to join a user's feature summary with order history, support tickets, or CRM data for offline analysis.Wide array/vector columns hurt the query planner; keep the hot online copy in a KV store and use the relational copy for analytics/joins only.
Key-value / vector DB, item_embeddings and recommendation_cacheAccess pattern is purely "get by id" - a vector for ANN search, or a cached list by cache_key - at very high scale.No ad-hoc joins; if a request needs "recs joined with live inventory," that join happens in the ranking service's application code, not the store.

7. Deep dives interviewers actually probe

How do you recommend anything for a brand-new user or item (cold start)?

For a new user with no interaction history, there's no personal embedding to search from, so the system falls back to content-based signals (category, brand, price band inferred from whatever context is available, like a signup survey or the landing page they arrived on) blended with globally popular items. For a new item with no purchase history, its embedding starts from item metadata (title, category, description) via a content encoder rather than co-purchase signal, and it's given an exploration boost - shown to a small slice of traffic even with a low predicted score - so the system can collect enough interaction data to learn a real embedding, often framed as a multi-armed bandit problem between exploiting known-good items and exploring new ones.

Why two-stage retrieval + ranking instead of scoring the entire catalog?

The ranking model is intentionally expensive per item - it looks at dozens of user/item/context features - which is affordable on 500 candidates but not on 350 million. An ANN index accepts a small, tunable recall loss (it might miss a few of the true best matches) in exchange for turning an O(catalog size) linear scan into an O(log n)-ish graph traversal, which is the only way candidate generation fits inside a low single-digit-millisecond slice of the 100ms budget.

// approximate nearest neighbor via HNSW: descend graph layers, greedy best-first search
function annSearch(queryVec, k):
    candidates = entryPoint
    for layer in topLayer downTo 0:
        candidates = greedySearchLayer(queryVec, candidates, layer, ef=200)
    return topK(candidates, k)   // ~500 candidates out of 350,000,000 items

How do you stop recommendations from becoming a popularity filter bubble?

Left unchecked, a ranker trained on past clicks reinforces whatever was already popular, since popular items have the most training signal. Two common levers: reserve a small exploration slot per request (epsilon-greedy - with probability epsilon, swap in a lower-ranked or under-shown item instead of the top-scored one) so the model keeps getting feedback on items it's unsure about; and apply a diversity penalty at ranking time that down-weights an item's score the more times it's already been shown to this user this session, which both improves perceived variety and generates the exploration data needed to keep the model honest.

How stale can embeddings get before quality visibly degrades, and how do you pick retraining cadence?

Item embeddings drift slowly (a product's co-purchase neighborhood rarely changes hour to hour), so nightly batch retraining is usually fine for embeddings; user-level personalization is where staleness bites first, because a user's intent can shift within a single session - which is exactly why the online feature store exists, letting very recent clicks nudge ranking within the current session even though the underlying embeddings won't update until the next nightly run. The decision is a cost/benefit trade: continuous training needs constant validation infrastructure and risks silently shipping a regression, while batch retraining concentrates that risk into one reviewable nightly job with an offline metric gate.

How do you A/B test a new ranking model without risking revenue?

The rollout in the offline training sequence diagram is staged specifically to de-risk this: first shadow traffic (the new model scores real requests but its output is logged, not shown, so you can compare its rankings against production offline), then a small canary (5% of live traffic) with guardrail metrics watched in near-real-time (click-through rate, add-to-cart rate, and critically revenue-per-session, not just an offline NDCG number), and only then a ramp to 100% - with a held-out control group kept on the old model throughout so the comparison stays statistically clean and any regression can be rolled back by simply routing traffic back to the previous model registry version.

8. Summary: what a strong answer covers

Clarified item-vs-user recs and scopeJustified every number with a calculationTwo-stage retrieval + ranking, not brute force Separated online serving from offline trainingMade the feedback loop async and non-blockingHandled cold start and filter-bubble explicitly
Interview tip When asked to design a recommendation system, the strongest signal is naming the catalog-size constraint out loud and using it to justify the two-stage architecture, rather than jumping straight to "use a neural network" - the system design, not the model architecture, is what's actually being evaluated.
No comments
Leave a Comment