InterviewKata
System design · Worked design D39 of 46 · Social and content · Long form

Design a social graph service

Design the service that stores who follows whom for a social network with two billion users: follow and unfollow, lists of followers and following, "do I follow this person?", counts, and mutual connections. Every feed, profile and notification in the product asks it questions, so it must answer in a few milliseconds, even for an account with 150 million followers.

1. The prompt

"Design the follow graph for a social app: users follow each other, see their followers and who they follow, and other services ask the graph questions."

What makes it hard:

2. Frame the problem

Follow (one-way) or friend (two-way)?

One-way follows, like most public social apps. A friendship is two follows that must both exist.

One-way edges are simpler to store; mutual relationships become a query (both edges exist).

How big?

2 billion users, 400 million daily actives. An average of 200 following each, so about 400 billion edges.

Far too many for one machine: the graph must be sharded.

What questions do callers ask?

Does A follow B? A's following list, B's followers list (newest first, paginated), the two counts, and mutual follows between two users.

Each question decides an access path. Note what's missing: no arbitrary multi-hop traversals on request.

Read and write volume?

About 2 million graph reads a second at peak, and about 2,000 follows or unfollows a second.

The design is read-optimised; writes can do extra work to make reads cheap.

How fresh must reads be?

A user must see their own follow at once. Others may see it a second or two later.

Read-your-writes for the actor; eventual consistency for everyone else.

Blocks and private accounts?

Yes: a block removes edges both ways and stops new ones; private accounts approve followers.

Pending requests are a separate edge type; blocks are checked on every write.

3. What it must do

  1. Follow and unfollow, including follow requests for private accounts and blocks.
  2. Answer point questions fast: does A follow B, and how many followers and followings does someone have.
  3. Return lists: someone's followers or following, newest first, a page at a time.
  4. Find mutual follows between two users ("followed by Chen and 12 others you know").

Not now: "people you may know" (an offline job over this graph), the feed itself (D06), ranking of followers.

4. How well it must do it

TargetWhy this number
Point reads under 5 ms at p99; list pages under 20 ms speedCalled many times while building one screen.
Read your own write immediately; others within a few seconds consistencyTapping Follow and not seeing it would look broken; a follower count a second behind doesn't.
The two directions agree eventually, always correctnessAn edge in Asha's list but missing from Ben's must be repaired, not left behind.
Availability 99.99% for reads surviving failuresMost of the product can't render without it.

5. Numbers that change the design

QuantityWorking→ so we…
Edges2 B users × 200 ≈ 400 B edges; stored twice (both directions) ≈ 800 B rows × ~40 bytes ≈ 32 TB, before replicas and indexes→ so we shard by user ID across a few hundred database machines
Reads2 M/s, highly skewed toward popular accounts and active users→ so a cache tier answers almost all reads; the databases see only misses and writes
CountsCounting 150 M rows per profile view is impossible→ so counts are stored and updated on each follow, never computed on read
The largest account150 M followers ≈ 6 GB of follower rows on one shardOne shard holds a celebrity's whole follower list → so lists are always paginated by cursor, and the hottest accounts' counters are split

Illustrative numbers.

6. The nouns

edge_out   (src_id, type, created_at DESC, dst_id) → state   sharded by src_id   "who does src follow?"
edge_in    (dst_id, type, created_at DESC, src_id) → state   sharded by dst_id   "who follows dst?"
           plus a unique index (src_id, type, dst_id) for "does A follow B?"
counts     (user_id, type) → following, followers           sharded by user_id (split for the largest accounts)
outbox     (shard-local) pending inverse writes and count changes

7. The contract

PUT    /v1/users/{me}/following/{target}        → 200 {state: following | requested}
DELETE /v1/users/{me}/following/{target}        → 204
GET    /v1/users/{id}/following?cursor=…&limit=50   → {users[], next_cursor}
GET    /v1/users/{id}/followers?cursor=…&limit=50   → {users[], next_cursor}
GET    /v1/users/{id}/counts                    → {followers, following}

Internal (other services)
  CheckEdges(src, [dst…])     → which of these does src follow?   (batched; up to 500 at once)
  Mutual(viewer, target, k)   → up to k people the viewer follows who also follow the target

Follow and unfollow are idempotent (PUT and DELETE), so a retried tap can't create two edges. Batched checks matter: building a feed of 50 posts asks "do I follow each author?" in one call, not 50.

8. First working version

A graph service in front of a sharded edge store, with a cache tier for reads. Every edge is written twice, once on each user's shard.

8.1 Asha follows Ben

8.2 Reading through the cache

Cache entries are whole small lists and point answers keyed by user (out:asha:follows, count:ben). A write updates the database and then deletes or updates the affected cache entries, so the next read refills them (Module 09).

Parked for laterkeeping both directions in step (9.1) · where edges live (9.2) · celebrities (9.3) · mutual follows (9.4) · regions and read-your-writes (9.5) · graph databases (9.6).

9. Make it hold up

9.1 Keeping both directions in step correctness

ApproachWhy
✗ BadThe service writes Asha's shard, then Ben's, and hopesA crash between the two writes leaves the edge in one list only, for ever.
~ GoodA distributed transaction across both shards (two-phase commit)Correct, but every follow now waits for two shards and a coordinator, and a slow shard blocks the other.
✓ GreatWrite one side plus an outbox row in one local transaction; a relay applies the inverse edge and the count changes, retrying until they succeed. Inverse writes are idempotent (an upsert keyed by both IDs)The follow commits on one shard, so it's fast. The other side follows within a second and can't be lost. A background checker compares the two directions and repairs any difference.

Unfollow uses the same path with a delete. If a follow and an unfollow race, compare their timestamps (or a version per edge) and keep the later one on both sides.

9.2 Where does an edge live? speed

9.3 An account with 150 million followers hot spots

9.4 Mutual follows speed

"Which of the people I follow also follow this account?" is the intersection of my following list (hundreds) with the target's followers list (possibly millions).

ApproachWhy
✗ BadRead both lists in full and intersect themReads millions of rows for one line of text on a profile.
~ GoodRead the smaller list, then check each of its members against the larger one"Does C follow the target?" for each of my 300 followings is 300 point lookups, batched by shard. Fast enough on request.
✓ GreatProbe from the smaller side, stop after k matches, and cache the result per (viewer, target) for a few hoursA profile shows "Chen and 12 others": we need the first few names and a count, not every match. Precise counts for huge lists can come from an offline job.

Compare the costs in the lab in section 13.

9.5 Regions and reading your own follow consistency

Each shard has one leader region for writes and replicas (database and cache) in every region. Reads are served locally. After Asha follows Ben, her own reads for the next few seconds are routed to the leader (or her region's cache is updated as part of the write), so her button and lists change at once, while other users' views catch up through replication. This is the approach Meta described for TAO: a write-through cache in the leader region, and caches elsewhere refreshed as replication arrives.

9.6 Why not a graph database? architecture

Graph databases (Neo4j, Neptune and others) are excellent at multi-hop questions over a graph that fits a cluster with good locality. A social graph's hot path is different: one hop, enormous volume, uneven degrees, and data that doesn't partition cleanly (any user can follow any other). Large social networks have instead stored adjacency lists in sharded relational or key-value stores with a cache tier in front: Twitter's FlockDB and Meta's TAO both took this shape. Multi-hop work ("friends of friends" for suggestions) runs offline over a snapshot, in a batch or graph-processing system.

9.7 The whole design after the deep dives

10. Interviewer follow-ups

Asha blocks Ben.

One write creates the block edge and, through the same outbox, removes follows in both directions and fixes both users' counts. Follow requests check for a block in either direction on the target's shard before creating an edge.

A bot follows 50,000 accounts an hour.

Rate limits per user on follow writes (with stricter limits for new accounts), and abuse signals that can hide or remove the edges in bulk. Counts shown to users can exclude edges flagged as spam.

Does Ben get notified?

The relay that writes the inverse edge also publishes a Followed event; the notification service subscribes. The graph service doesn't call it directly.

Count and list disagree.

They can, briefly, because counts are updated asynchronously. A periodic job recounts a sample of users (and every large account) from the edge rows and corrects drift.

A user deletes their account.

Mark the user deleted at once (hidden everywhere), then a background job pages through both their lists and removes each edge on both sides, updating the other users' counts. For a celebrity this takes hours, so it must be resumable.

Ten times the users.

More shards (consistent hashing or a directory of shard ranges, so data moves gradually) and more cache servers. Nothing in the design needs a global view.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
Edge shard leader downFollows on that shard fail brieflyWrite errors per shardReplica promoted; reads continue from replicas and cache
Outbox relay behindFollowers lists and counts lagOutbox ageActor still sees their own follow; relay scales out
Cache tier lost in a regionSlower reads; databases floodedHit rate, DB loadRequest coalescing, rate-limited refill, other cache replicas
Directions disagreeAn edge missing in one listConsistency checkerIdempotent repair from the authoritative side
Celebrity follow stormCount updates lagHot-key metricsSplit counters, batched increments

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch: read latency by call type · cache hit rate · outbox age · direction mismatches found by the checker · hot keys · follow write errors and rate-limit rejections.

At 10×: more shards and cache servers; the per-user layout scales linearly. The largest accounts grow faster than the rest, so hot-key handling matters more over time.

A 60-minute interview

0–5Frame: follow vs friend, size, which questions
5–10Must do, how well, numbers
10–15Nouns: two edge tables, counts; the contract
15–25First version: the follow write and cached reads
25–50Deep dives: two directions (9.1), sharding (9.2), celebrities (9.3)
50–60Mutual follows or regions, follow-ups, wrap-up

A 90-minute interview

0–8Frame, including blocks and private accounts
8–15Must do, how well, numbers
15–22Nouns, contract
22–35First working version
35–75Deep dives 9.1–9.6 and the final picture
75–85Follow-ups: blocks, bots, deletion
85–90Trade-offs, metrics, 10×
LevelWhat a strong answer shows
Mid-levelAn edges table with the right indexes, follow and unfollow, paginated lists, a cache.
SeniorBoth directions stored and sharded by owner, an outbox to keep them in step, stored counts, cursor paging, hot keys, mutual follows from the smaller side.
Staff+Consistency checking and repair as part of the design, read-your-writes across regions, abuse and deletion at scale, why online traversal is avoided and what runs offline instead.

Common mistakes:

13. Try it, check yourself, read more

The mutual follows lab. Set the sizes of the two lists and how many names the profile shows, and compare the ways to find mutual follows.

I follow
The target's followers
Overlap
ApproachRows readRough time

Illustrative costs: ~1 µs per row scanned in a list, ~0.3 ms per batched round trip of up to 100 point lookups. "Stop after 3" shows the first three names, as a profile does.

How do you serve both "who does Asha follow?" and "who follows Ben?" from single shards?

Each user's lists live with that user; the price is a second write.

How do you keep the two copies of an edge in step without a distributed transaction?

The outbox makes the second write certain to happen eventually; idempotent upserts make retries safe.

Where does a follower count of 150 million come from?

Never count on read; spread write contention on hot counters.

The fastest way to show "followed by Chen and 12 others you know"?

Read only what the screen needs; cache the result.
ResourceWhy
TAO: Facebook's distributed data store for the social graph (USENIX ATC 2013)Objects and associations, inverse edges, and a two-tier, multi-region cache.
TAO: The power of the graph (Meta Engineering)A shorter introduction to the same system.
FlockDB (Twitter, archived)Adjacency lists in sharded MySQL, built for huge, shallow graphs.