01

Requirements & scope

Problem Statement & Requirements

Design a social graph system that models relationships between users and powers core social features — friend/connection management, "People You May Know" suggestions, mutual connections, degree-of-separation queries, and graph-based feed ranking — similar to LinkedIn's connection graph, Facebook's social graph, or Twitter's follower graph.

Functional Requirements

  • Add / remove connection — send connection request, accept/reject, remove existing connection
  • Follow / unfollow — asymmetric follow relationship (Twitter model) alongside symmetric connections (LinkedIn/Facebook model)
  • Get connections — list a user's 1st-degree connections with pagination
  • Mutual connections — "You and Alice have 12 mutual connections"
  • Degree of separation — "Alice is a 2nd-degree connection" (friend of a friend)
  • People You May Know (PYMK) — recommend new connections based on graph proximity, shared attributes, mutual connections
  • Connection count — display connection/follower/following counts
  • Graph search — "Find people named Bob who work at Google and are connected to Alice"
  • Privacy controls — control who can see your connections, who can send requests
  • Block user — hide from each other's graph entirely

Non-Functional Requirements

  • Low latency — connection lookup < 10 ms; mutual connections < 50 ms; PYMK < 200 ms
  • High read throughput — 500K+ graph queries/sec (connections list, mutual, PYMK)
  • Consistency — connection state must be strongly consistent (if I connect, both parties see it immediately)
  • Scale — 1B+ users, 500B+ edges (connections + follows)
  • Availability — 99.99% for read operations
  • Efficient traversal — 2nd and 3rd degree queries must be fast despite billions of edges

Out of Scope

  • News feed generation (see News Feed design)
  • Messaging system (see Chat System design)
  • Profile / content management
  • Full knowledge graph (entity relationships beyond people)
02

Scale estimations

Scale Estimations

Traffic

MetricValue
Total users1B
Monthly active users500M
Average connections per user500 (LinkedIn avg ~930 for active users)
Total edges (bidirectional connections)1B × 500 / 2 = 250B edges
Follow edges (asymmetric)250B additional edges
Total graph edges~500B
Connection list reads / second200K RPS
Mutual connection queries / second100K RPS
PYMK queries / second50K RPS
Connection requests / second5K RPS (writes)
Graph search queries / second30K RPS
Total graph queries / second~400K RPS

Storage

MetricValue
Edge record size32 bytes (user_id_a: 8B, user_id_b: 8B, type: 1B, created_at: 8B, metadata: 7B)
Total edge storage500B × 32 B = ~16 TB
Adjacency list (per user, avg 500 connections)500 × 8 B = 4 KB per user
Total adjacency lists1B × 4 KB = ~4 TB
User profile index for graph search1B × 200 B = ~200 GB
PYMK precomputed suggestions cache500M MAU × 1 KB = ~500 GB

Bandwidth

MetricValue
Connection list responses (200K/s × 5 KB)~1 GB/s
Mutual connection queries (100K/s × 500 B)~50 MB/s
PYMK responses (50K/s × 2 KB)~100 MB/s
Total read bandwidth~1.2 GB/s

Hardware Estimate

ComponentSpec
Graph store (adjacency lists)50-100 nodes (in-memory sharded)
Edge store (persistent)30-50 shards (SSD-backed)
PYMK computation cluster20-50 nodes (batch + incremental)
Cache (Redis)20-50 nodes (hot user adjacency lists)
Graph search index (Elasticsearch)30-50 nodes
03

Layered architecture

Phase 1 of 4 · Client & edge

Start with the entry points

First, define who uses the system and where requests enter. This is the boundary of the design, not yet a complete request path.

Client
Distinguish directed follows from mutual connections and define visibility rules.
API gateway
Authorize relationship reads as well as writes; graph reachability does not imply permission.
Architecture after phase 1. Includes Client, API gateway. Client connects to API gateway.
Phase 1 — Start with Client, API gateway.SVGSVGEditable diagramEditable diagram

This first layer defines the system boundary. The next chapters add application logic and data dependencies. After examining the request paths and tradeoffs, we will add operational safeguards and bring the complete architecture together.

04

API & contracts

Phase 2 of 4 · Application & compute

Add the application logic

Keep the entry points and add the services that do the work. Give each service a clear responsibility before defining its contracts. Storage and background dependencies come next.

Connection service
Create or remove relationship edges with conditional state changes and stable request IDs.
Graph query service
Use bounded traversals and adjacency intersections rather than unbounded graph scans.
Architecture after phase 2. Includes Client, API gateway, Connection service, Graph query service. Client connects to API gateway. API gateway connects to Connection service. API gateway connects to Graph query service.
Phase 2 — Added Connection service, Graph query service. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

API Design

Send Connection Request

EXAMPLE
POST /api/v1/connections/request
Authorization: Bearer <token>

Request:
{
  "target_user_id": "user-bob-456",
  "message": "Hi Bob, we met at the conference!"   // optional
}

Response (201 Created):
{
  "request_id": "req-abc123",
  "status": "pending",
  "from": "user-alice-123",
  "to": "user-bob-456",
  "created_at": "2026-04-03T10:00:00Z"
}

Accept / Reject Connection

EXAMPLE
POST /api/v1/connections/request/{request_id}/accept

Response (200 OK):
{
  "connection_id": "conn-xyz789",
  "users": ["user-alice-123", "user-bob-456"],
  "connected_at": "2026-04-03T10:05:00Z"
}

POST /api/v1/connections/request/{request_id}/reject
Response: 204 No Content

Get Connections (Adjacency List)

EXAMPLE
GET /api/v1/users/{user_id}/connections?limit=20&cursor=eyJsYXN0IjoiY29ubl8xMjM0NTY3In0=

Response (200 OK):
{
  "user_id": "user-alice-123",
  "total_connections": 847,
  "connections": [
    {
      "user_id": "user-bob-456",
      "name": "Bob Smith",
      "headline": "VP Engineering at Acme Corp",
      "profile_image": "https://cdn.example.com/bob.jpg",
      "connected_at": "2026-04-03T10:05:00Z",
      "mutual_connection_count": 12
    },
    ...
  ],
  "next_cursor": "eyJsYXN0IjoiY29ubl85OTk5In0="
}

Get Mutual Connections

EXAMPLE
GET /api/v1/users/{user_id_a}/mutual/{user_id_b}?limit=10

Response (200 OK):
{
  "user_a": "user-alice-123",
  "user_b": "user-charlie-789",
  "mutual_count": 12,
  "mutual_connections": [
    {
      "user_id": "user-bob-456",
      "name": "Bob Smith",
      "headline": "VP Engineering at Acme Corp"
    },
    ...
  ]
}

Get Degree of Separation

EXAMPLE
GET /api/v1/users/{user_id_a}/degree/{user_id_b}

Response (200 OK):
{
  "user_a": "user-alice-123",
  "user_b": "user-dave-999",
  "degree": 2,
  "path": [
    "user-alice-123",      // Alice
    "user-bob-456",        // → Bob (1st degree)
    "user-dave-999"        // → Dave (2nd degree via Bob)
  ]
}

People You May Know

EXAMPLE
GET /api/v1/users/{user_id}/pymk?limit=20

Response (200 OK):
{
  "suggestions": [
    {
      "user_id": "user-eve-111",
      "name": "Eve Johnson",
      "headline": "Data Scientist at BigCorp",
      "mutual_connections": 8,
      "shared_company": "Acme Corp",
      "shared_school": null,
      "score": 0.87,
      "reason": "8 mutual connections"
    },
    ...
  ]
}

Graph Search

EXAMPLE
GET /api/v1/search/people?q=product+manager
    &company=Google
    &location=San+Francisco
    &degree=2
    &limit=20

Response (200 OK):
{
  "total": 342,
  "results": [
    {
      "user_id": "user-frank-222",
      "name": "Frank Lee",
      "headline": "Sr. Product Manager at Google",
      "location": "San Francisco, CA",
      "degree": 2,
      "mutual_connections": 5,
      "relevance_score": 0.92
    },
    ...
  ]
}
05

Data model

Phase 3 of 4 · Data & async work

Connect data and background work

The services now need their dependencies: durable state, fast lookups, or work that continues outside a request. Separate these responsibilities rather than treating every component as a database.

Adjacency cache
Keep hot neighbor lists close to the query path while bounding cache staleness.
Edge store
Persist relationships independently of derived counts and caches.
Suggestion engine
Generate candidates offline, then rerank with current privacy and block constraints.
Architecture after phase 3. Includes Client, API gateway, Connection service, Graph query service, Adjacency cache, Edge store, Suggestion engine. Client connects to API gateway. API gateway connects to Connection service. API gateway connects to Graph query service. Graph query service connects to Adjacency cache. Connection service connects to Edge store. Graph query service connects to Suggestion engine.
Phase 3 — Added Adjacency cache, Edge store, Suggestion engine. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

Data Model

Edge Table (Persistent Store)

Edge Table (Persistent Store)
Edge Table (Persistent Store)SVGSVGEditable diagramEditable diagram

Adjacency List (In-Memory Index)

Adjacency List (In-Memory Index)
Adjacency List (In-Memory Index)SVGSVGEditable diagramEditable diagram

Connection Request

Connection Request
Connection RequestSVGSVGEditable diagramEditable diagram
06

Core design decisions

Core Design Decisions

Decision 1: Graph Storage — How to Store 500B Edges

Option A: Relational Database (MySQL/PostgreSQL)
Option A: Relational Database (MySQL/PostgreSQL)
Option A: Relational Database (MySQL/PostgreSQL)SVGSVGEditable diagramEditable diagram
ProsCons
Familiar, well-understoodJoins for mutual connections = slow at scale
ACID transactionsMulti-hop traversals (2nd, 3rd degree) = expensive
Easy sharding by user_idShard-crossing for reverse lookups
Indexes handle single-hop wellFan-out for 2nd degree: 500 × 500 = 250K lookups
Option B: Native Graph Database (Neo4j / JanusGraph)
Option B: Native Graph Database (Neo4j / JanusGraph)
Option B: Native Graph Database (Neo4j / JanusGraph)SVGSVGEditable diagramEditable diagram
ProsCons
O(1) per hop traversal (index-free adjacency)Harder to shard (graph partitioning is NP-hard)
Natural query language for graph patternsLess mature for 500B+ edges at write throughput
Multi-hop queries are first-classOperational complexity (less tooling than SQL)
Beautiful for 2nd/3rd degree, PYMKExpensive at massive scale (licensing/infra)
Option C: Adjacency List in KV Store + SQL Edge Table (LinkedIn/Facebook Approach)
Option C: Adjacency List in KV Store + SQL Edge Table (LinkedIn/Facebook Approach)
Option C: Adjacency List in KV Store + SQL Edge Table (LinkedIn/Facebook Approach)SVGSVGEditable diagramEditable diagram
ProsCons
Sub-ms reads from memoryTwo stores to keep in sync
SQL durability for writesMemory cost (~4 TB for full graph)
Set intersection for mutuals is trivialRebuilding adjacency list from edge table on miss
Proven at Facebook/LinkedIn scaleMore infrastructure complexity
Recommendation: Option C (Adjacency in Memory + SQL Edges)
EXAMPLE
This is what Facebook (TAO) and LinkedIn (follow graph) actually use.
In-memory adjacency for reads (hot path), SQL for writes (durability).
Graph DBs are great for exploration queries but harder to shard at 500B edges.

Decision 2: Mutual Connection Computation

Decision 2: Mutual Connection Computation
Decision 2: Mutual Connection ComputationSVGSVGEditable diagramEditable diagram

Decision 3: Degree of Separation — BFS on Billion-Node Graph

Decision 3: Degree of Separation — BFS on Billion-Node Graph
Decision 3: Degree of Separation — BFS on Billion-Node GraphSVGSVGEditable diagramEditable diagram
07

Request flows

Detailed Flow Diagrams

Connection Request → Accept Flow

Connection Request → Accept Flow
Connection Request → Accept FlowWide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram

Mutual Connections Query

Mutual Connections Query
Mutual Connections QuerySVGSVGEditable diagramEditable diagram

PYMK Batch Computation

PYMK Batch Computation
PYMK Batch ComputationSVGSVGEditable diagramEditable diagram
08

Performance & caching

Neighbor-list caches make common graph reads cheap, but very high-degree users can still dominate memory and intersection work. Fetch bounded subsets where the product allows it and choose intersection algorithms suited to list sizes. Recheck privacy and block rules before displaying a cached relationship or suggestion.

09

Advanced design

Graph Search

Graph Search
Graph SearchSVGSVGEditable diagramEditable diagram
10

Edge cases

Handling Edge Cases

Celebrity / High Fan-Out Users

EXAMPLE
Problem: Elon Musk has 150M followers
  → Adjacency list: 150M × 8 bytes = 1.2 GB for ONE user
  → Mutual connections with 150M: sort-merge = very expensive

Solution: Tiered Storage

  Normal users (< 50K connections):
    → Full adjacency list in memory (standard path)
    → Real-time set intersection for mutuals

  High fan-out users (> 50K followers):
    → Followers stored in persistent store only (not in-memory)
    → Follower count maintained as counter (not list length)
    → Mutual connections: use bloom filter approximation
    → "~23 mutual connections" (approximate, not exact list)
    → Exact mutual list computed on-demand (paginated, not all at once)

  Bidirectional connections (LinkedIn-style) naturally bounded:
    → Max ~30K connections on LinkedIn
    → Always fits in memory
    → Followers (asymmetric) can be unlimited → tiered

Consistency on Connection Create

EXAMPLE
Problem: Alice connects to Bob
  → Must update: edge table, Alice's adjacency, Bob's adjacency
  → If Alice's adjacency updates but Bob's fails → inconsistent

Solution: Event-Sourced Writes

  1. Write to edge table (source of truth) — atomic DB transaction
  2. Publish "connection_created" event to Kafka
  3. Adjacency list updater consumes event:
     → Update Alice's list (retry on failure)
     → Update Bob's list (retry on failure)
  4. If adjacency update fails:
     → Consumer retries (Kafka guarantees at-least-once)
     → Adjacency store is eventually consistent (seconds lag OK)
  5. Worst case: adjacency stale → user refreshes → reads from edge table

  Critical: Edge table write is the commit point.
  Adjacency lists are derived views (can always be rebuilt).

Graph Partitioning (Sharding)

Graph Partitioning (Sharding)
Graph Partitioning (Sharding)SVGSVGEditable diagramEditable diagram

Blocking and Privacy

EXAMPLE
Block user:
  1. INSERT edge (A, B, type=blocked)
  2. Remove connection edge if exists: DELETE (A,B) and (B,A)
  3. Update adjacency lists: remove from both
  4. Block record checked on ALL graph operations:
     → A never appears in B's search results, PYMK, or mutual lists
     → B never appears in A's search results, PYMK, or mutual lists
  5. Bloom filter of blocked pairs checked before any result returned
     → Fast O(1) check, minimal false positives

Privacy settings:
  "Only connections can see my connections list"
  → Graph Query Service checks viewer's relation to target
  → If viewer is not connected AND target's setting = private:
    → Return: "Connection list is private" (not the data)
  → Applied at API layer, not storage layer
11

Tradeoffs

Tradeoffs & Design Decisions Summary

DecisionOption AOption BChosenWhy
Graph storageSQL edge table onlyGraph DB (Neo4j)Hybrid: in-memory adjacency + SQL edgesIn-memory for sub-ms reads; SQL for durable writes; proven at LinkedIn/FB scale
Adjacency representationOn-disk (B-tree index)In-memory sorted arraysIn-memory< 0.5 ms reads; 4 TB fits in cluster; the graph IS the cache
Mutual connectionsSQL JOINIn-memory set intersectionIn-memory merge-intersectO(n+m) on sorted arrays = microseconds; SQL join on 500B row table = seconds
Degree of separationUnidirectional BFSBidirectional BFSBidirectional + sampling5,700× faster than unidirectional; sampling keeps depth-3 practical
PYMKReal-time per requestBatch + incremental updatesBatch nightly + incrementalReal-time for 500M users = infeasible; batch computes, events update incrementally
Edge storageSingle row per connectionTwo rows (A→B, B→A)Two rows2× storage but each user's adjacency scan is a simple prefix query (no reverse index needed)
ShardingSocial-cluster partitioningHash-basedHash + cachingHash is simple, even; caching adjacency lists eliminates cross-shard traversal penalty
High fan-outFull adjacency in memoryTiered (memory for connections, disk for followers)Tiered150M-follower users can't fit in memory; bloom filter for approximate mutuals
Graph searchGraph DB nativeElasticsearch + graph overlayES + graph boostingES proven for text search at scale; graph annotations added as re-ranking layer
ConsistencySynchronous dual-writeEvent-sourced (write edges → event → update adjacency)Event-sourcedEdge table is commit point; adjacency is derived, eventually consistent (seconds)
12

Reliability & fault tolerance

Phase 4 of 4 · Operations & resilience

Add operational safeguards

With the request paths and tradeoffs established, add the controls that help detect trouble and keep the system operating. The final diagram retains every layer introduced so far.

Event bus
Publish graph changes so caches, counts, and suggestions can converge and be rebuilt.
Architecture after phase 4. Includes Client, API gateway, Connection service, Graph query service, Adjacency cache, Edge store, Suggestion engine, Event bus. Client connects to API gateway. API gateway connects to Connection service. API gateway connects to Graph query service. Graph query service connects to Adjacency cache. Connection service connects to Edge store. Graph query service connects to Suggestion engine. Edge store connects to Event bus. Event bus connects to Adjacency cache.
Phase 4 — Added Event bus. Earlier layers remain in place.SVGSVGEditable diagramEditable diagram

Reliability & Fault Tolerance

Single Points of Failure & Mitigations

Single Points of Failure & Mitigations
Single Points of Failure & MitigationsSVGSVGEditable diagramEditable diagram

Graceful Degradation

EXAMPLE
Tier 1 (PYMK service down):
  → Profile page shows no suggestions
  → Core connections/search unaffected
  → PYMK rebuilds when service recovers

Tier 2 (Graph search degraded):
  → Fall back to text-only search (no graph ranking)
  → Results still useful, just not personalized by degree
  → "N mutual connections" badge missing

Tier 3 (Adjacency store shard down):
  → Connections for affected users fetched from edge table (slower, ~20 ms)
  → Read replica of adjacency store serves reads if available
  → Mutual connections degrade to approximate (bloom filter) or unavailable

Tier 4 (Edge table shard down):
  → Cannot create new connections (writes fail)
  → Existing adjacency lists still serve reads (in-memory)
  → Auto-failover to standby (< 30s)
13

Production architecture

High-Level Architecture

High-Level Architecture
High-Level ArchitectureSVGSVGEditable diagramEditable diagram

Component Breakdown

Component Breakdown
Component BreakdownSVGSVGEditable diagramEditable diagram

Full System Architecture (Production-Grade)

Full System Architecture (Production-Grade)
Full System Architecture (Production-Grade)Wide diagram — scroll horizontally.SVGSVGEditable diagramEditable diagram

Graph Algorithms Used in Production

Graph Algorithms Used in Production
Graph Algorithms Used in ProductionSVGSVGEditable diagramEditable diagram
14

Further exploration

Facebook TAO — Reference Architecture

Facebook TAO — Reference Architecture
Facebook TAO — Reference ArchitectureSVGSVGEditable diagramEditable diagram
15

Interview playbook

Interview Tips

  1. Start with the data model: two rows per bidirectional connection — Store edges (A→B) and (B→A). 2× storage but each user's connection list is a simple prefix scan. This is the non-obvious insight that shows you've thought deeply about read patterns.

  2. In-memory adjacency lists are the key performance decision — "500B edges × 32 bytes = 16 TB on disk. But adjacency lists (sorted user_id arrays) = 4 TB, which fits in a 100-node cluster's RAM. Sub-millisecond reads, set intersection for mutuals in microseconds." This is how Facebook TAO and LinkedIn actually work.

  3. Mutual connections = set intersection, not SQL JOIN — Load A's sorted array and B's sorted array. Merge-intersect in O(n+m). For 500 + 800 connections = ~1,300 comparisons = microseconds. SQL JOIN on a 500B-row table would take seconds.

  4. Bidirectional BFS for degree of separation — Don't do naive BFS from one end. Expand from BOTH endpoints, meet in middle. O(b^(d/2)) vs O(b^d) = 5,700× faster for depth 3 with branching factor 500. Limit to 3rd degree (LinkedIn does "3rd+").

  5. PYMK is the hardest problem — Can't compute in real-time for 500M users. Explain: nightly batch job (Spark, ~5 hours) computes 2nd-degree candidates, scores by mutual count + shared attributes. Incremental updates via Kafka events when connections change. Serve from Redis cache.

  6. Celebrity/influencer problem — Normal users have ~500 connections (fits in memory). Elon has 150M followers (1.2 GB). Solution: tiered storage — connections in memory, follower lists on disk. Approximate mutual count via bloom filters for high fan-out users.

  7. Graph sharding is hard — Graph partitioning is NP-hard. Don't try social-cluster partitioning. Use hash sharding (simple, even) and aggressive caching of adjacency lists. The adjacency list IS the cache — 4 TB fits in memory.

  8. Event-sourced writes for consistency — Edge table write is the commit point (ACID). Adjacency list updates are derived via Kafka events (eventually consistent, seconds lag). If adjacency store fails, edges still safe in DB; adjacency rebuilt.

  9. Graph search = Elasticsearch + graph re-ranking — Don't build a graph-native search engine. Use ES for text/attribute search, then re-rank results by graph distance (1st degree = boost 3×, 2nd degree = boost 2×, mutual count). Two systems, each doing what it does best.

  10. Reference Facebook TAO — "Facebook TAO: in-memory cache (99.8% hit rate, < 1 ms reads) backed by sharded MySQL. Billions of reads/sec. Writes go to MySQL first, then invalidate cache. Scale reads by adding cache nodes. This is the architecture I'm following." Instant credibility.