Overview
A friend recommendation service suggests new connections to users by combining graph proximity signals, shared activity, and demographic affinity. The system must generate fresh, diverse candidate lists at interactive latency while avoiding redundant suggestions of existing connections or previously dismissed candidates.
Requirements
Functional Requirements
- Return a ranked list of up to N recommended users for a given requester.
- Exclude existing connections and users the requester has dismissed.
- Refresh recommendations at least once per day per active user.
- Support real-time re-ranking when the user performs a new connection action.
Non-Functional Requirements
- Recommendation retrieval latency under 200 ms at p95.
- Candidate generation pipeline processes hundreds of millions of users per day.
- Diversity constraints ensure candidates span multiple signal categories.
- System remains available during partial graph service degradation.
Data Model
Each user has a pre-computed candidate table keyed by user ID. Each row stores a candidate user ID, a composite score, a breakdown of contributing signals, a generation timestamp, and a dismissal flag. The table is stored in a key-value store with TTL-based expiry set to 48 hours, triggering regeneration for the next request after expiry.
A dismissal log records (requester_id, dismissed_user_id, timestamp) and is consulted during candidate filtering. Dismissal records are retained for 90 days and stored in a time-sorted set per user for efficient range queries.
The graph adjacency store provides second-degree neighbor lists, which are the primary seed for graph-proximity candidates. Activity co-occurrence data is stored in a separate column-family table mapping (user_id, co_event_type, other_user_id) to an interaction count.
Core Algorithms
Candidate Generation
Candidates are sourced from multiple retrieval channels and merged before ranking:
- Graph proximity: second-degree neighbors reachable in exactly two hops, sorted by mutual connection count descending.
- Activity co-occurrence: users who liked, commented on, or shared the same content within a rolling 30-day window.
- Community membership: members of the same groups or interest communities not yet connected.
- Collaborative filtering: users similar to the requester in embedding space, retrieved via approximate nearest-neighbor search on pre-computed user embeddings.
Each channel contributes up to K candidates. Duplicates across channels are merged, and the union is deduplicated against the existing connection set and dismissal log using a Bloom filter for O(1) membership checks.
Scoring and Ranking
Each candidate receives a composite score computed as a weighted linear combination of normalized signals:
- Mutual friend count normalized by the geometric mean of both users degree.
- Activity overlap score from co-occurrence counts normalized by total activity.
- Graph distance penalty: candidates at distance two score higher than distance three.
- Recency bonus for candidates who recently joined or became active.
Weights are tuned offline using a learning-to-rank model trained on historical accept and dismiss events. Online A/B experiments evaluate weight updates before full rollout.
Diversity Re-Ranking
After initial scoring, a diversity pass applies Maximal Marginal Relevance (MMR) to reduce redundancy. For each position in the output list, the algorithm selects the candidate that maximizes a blend of relevance score and dissimilarity to already-selected candidates. Dissimilarity is measured using cosine distance on user embedding vectors, ensuring the list spans multiple interest clusters rather than returning a homogeneous group.
API Design
GetRecommendations(user_id, limit, context)— returns a ranked candidate list with score explanations.DismissCandidate(user_id, candidate_id, reason)— records a dismissal and triggers partial re-ranking.RefreshRecommendations(user_id)— forces cache invalidation and synchronous regeneration for testing.RecordAction(user_id, candidate_id, action_type)— logs accept or ignore for model training.
Scalability
Offline Pipeline
A daily batch pipeline runs on a distributed compute cluster. It shards the user population by user ID range, computes candidates and scores for each shard in parallel, and writes results to the candidate store. The pipeline uses incremental processing where possible, regenerating only users whose graph neighborhood changed significantly since the last run.
Online Serving
The serving layer retrieves pre-computed candidates from the store, applies the dismissal filter in-process, and runs the lightweight diversity re-ranking step in real time. For users whose cache has expired, a synchronous fallback generates a reduced candidate list using only the cheapest signal (mutual friend count) within the latency budget.
Monitoring
Tracked metrics include candidate cache hit rate, acceptance rate by signal channel, diversity index of returned lists, and pipeline freshness lag. Alerts fire when cache hit rate drops below 80% or when acceptance rate diverges more than two standard deviations from baseline, indicating a model or data quality regression.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How do mutual friends serve as a signal in a friend recommendation system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Mutual friend count is a strong proximity signal: two users sharing many mutual connections are likely to know each other in real life. The system counts overlapping adjacency-list entries for a candidate pair, normalises by the Jaccard or Adamic-Adar coefficient to avoid bias toward high-degree nodes, and uses the result as a primary feature in the ranking model.”
}
},
{
“@type”: “Question”,
“name”: “How is graph proximity measured with BFS depth for friend recommendations?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “BFS depth measures the shortest social distance between two users. A depth of 2 means they share at least one mutual friend; depth 3 means a friend-of-a-friend. In practice the system runs a bidirectional BFS capped at depth 2 or 3 from the source user, collecting candidate nodes and annotating them with their minimum hop count, which feeds into a lighter-is-better ranking signal.”
}
},
{
“@type”: “Question”,
“name”: “What activity and interaction signals improve friend recommendation quality?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Beyond graph structure, behavioural signals include co-tagging in photos, comment or reaction exchanges, event co-attendance, shared group membership, and recency-weighted message history. These events are streamed into a feature store keyed by user-pair ID so the ranker can consume pre-aggregated interaction scores without real-time graph traversal.”
}
},
{
“@type”: “Question”,
“name”: “How does diversity-aware ranking with MMR work in friend recommendations?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Maximal Marginal Relevance (MMR) re-ranks an initial candidate list by iteratively selecting the candidate that maximises a weighted combination of relevance score and dissimilarity to already-selected recommendations. This prevents the final list from being dominated by a single social cluster, surfacing candidates from different walks of the user's life — work, school, local community — and improving long-term engagement.”
}
}
]
}
See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering
See also: Snap Interview Guide