Interview Setup
Interview Prompt
Design a follower/following system like Twitter. Users can follow and unfollow accounts, view paginated follower and following lists, inspect counter badges on profiles, and receive follow suggestions while handling celebrity accounts with 100M+ followers.
Clarifying Questions (ask before designing)
| Question | Why it matters |
|---|---|
| Is the relationship graph directed or bidirectional? | Follow relationships are strictly directed (A follows B does not imply B follows A), which dictates storage modeling and recommendation traversals. |
| What is the fan-out strategy for feed delivery? | Balancing fan-out-on-write vs fan-out-on-read determines whether the follow graph resides on the critical write path for posts. |
| How fresh must follower counts appear? | Eventual consistency with seconds of lag is acceptable for aggregate counters, whereas follow state must be immediately visible to the acting user. |
| Do we need to evaluate multi-hop queries in real time? | Single-hop checks are O(1) via partition lookups, whereas two-hop friend-of-friend queries require offline precomputation. |
Scope
In scope
- Follow and unfollow actions with block list enforcement
- Cursor-paginated follower and following lists
- Follower and following aggregate counts
- High-throughput is-following verification at 500K QPS
- Celebrity scaling patterns for accounts with 100M+ followers
- Social follow suggestions based on mutual connections
Out of scope (state explicitly)
- Graph storage internals and offline 2-hop batch
- Full news feed delivery engine
- Direct messaging infrastructure
- Account verification and badge management
Functional Requirements
Scope the follower/following product: follow and unfollow actions, paginated lists, and denormalized counts. For the underlying storage engine, see the Social Graph Store.
In the room: distinguish this design from the Social Graph Store, which handles distributed adjacency lists, because here you are designing the user-facing product API.
Variant of the Social Graph Store: focus on follow and unfollow APIs, is-following lookups at 500K QPS, and celebrity fan-out-on-read mechanics.
- Follow and Unfollow: Create or remove directed social connections between users.
- Follower List: Paginated collection of users following an account, sorted by follow recency.
- Following List: Paginated collection of accounts that a given user follows.
- Counter Badges: Maintain accurate follower and following totals on user profiles.
- Follow Suggestions: Generate "People you may know" recommendations via mutual connections.
- Status Check: Ultra-low latency "Does User A follow User B?" verification.
- Feed Fan-Out Integration: Route newly published posts to eligible follower inboxes.
- Activity Notifications: Dispatch push alerts when a user gains a new follower.
Non-Functional Requirements
Follow mutations must complete in under 100 ms, while follower lists must paginate efficiently under heavy read-to-write traffic ratios.
- Low Latency: Process follow and unfollow actions in under 100 ms p99.
- Scale: Accommodate celebrity accounts with over 100M followers without partition failure.
- Read-Your-Writes Consistency: Follow actions must immediately reflect to the initiating actor.
- Bounded Eventual Consistency: Displayed follower counts can lag underlying edges by a few seconds.
- High Availability: Deliver 99.99% service availability.
- Hot Spot Resilience: Absorb sudden spikes of 100K follows per second on trending profiles.
Capacity Estimations
Registered users, average follow relationships, and celebrity fan-out limits determine storage footprint and memory sizing.
| Metric | Calculation | Value |
|---|---|---|
| Total users | Given | 1B |
| Avg followers per user | Given (power-law: most users < 500, celebrities skew avg) | 200 |
| Total follow edges | 1B users x 200 avg followers | 200B |
| Follow events / day | Given (~0.2 follows/user/day) | 200M |
| Follow events / sec | 200M/day ÷ 86400 ≈ 2.3K (peak 50K = 20x) | ~2.3K (peak 50K) |
| Follow check queries / sec | Given (is-following on every profile/timeline render) | 500K |
| Edge record size | follower_id (8B) + followee_id (8B) + timestamp (8B) | 24 bytes |
| Edge storage | 200B edges x 24 bytes | ~4.8 TB |
| Celebrity fan-out writes/post | 100M followers x 64B feed entry | ~6.4 GB per celebrity post (fan-out-on-read above 100K threshold) |
Architecture Diagram
The system persists follow relationships in dual Cassandra tables, maintains aggregate counts asynchronously, and serves paginated follower and following lists from cache.
Component Deep Dives
Write Path: Follow Action
Walk through the complete follow and unfollow pipeline and how counter values remain consistent under concurrent mutations.
When Alice follows Bob, the system validates permissions (ensuring Alice is not blocked, is not following herself, and does not hold an existing edge), writes an atomic Cassandra BATCH across both tables, updates Redis sorted sets and counters, and publishes an event to Kafka. The entire write path executes in under 100 ms p99.
Follow write sequence:
1. Check block list: GET block:{alice}:{bob}, returning 403 if blocked
2. Idempotency: SET follow:idem:{key} NX EX 86400, returning cached response if key exists
3. Cassandra BATCH:
INSERT INTO following (alice, now, bob)
INSERT INTO followers (bob, bucket(alice), alice, now)
INSERT INTO is_following (alice, bob)
4. Redis pipeline: ZADD following:{alice}, ZADD followers:{bob} (if < 100K)
INCR follower_count:{bob}, INCR following_count:{alice}
5. Kafka publish: { event: "follow", actor, target, ts }
6. Return 201 CreatedEvent Bus (Kafka)
Follow writes return quickly after persisting to Cassandra and Redis. The Kafka follow-events topic drives asynchronous feed fan-out, push notifications, and suggestion index updates without adding latency to the client response.
Topic: follow-events
Partitions: 64
Partition key: followee_id (groups fan-out work per target user)
Retention: 7 days
Replication factor: 3, min.insync.replicas: 2
Producer: Follow Service after Cassandra BATCH + Redis cache update
Event: { event_id, event: "follow" | "unfollow", actor_id, target_id, timestamp }
Consumer groups:
1. feed-fanout: LPUSH new posts to follower feeds (skipped if target > 100K followers)
2. notification: push alerts for "Alice followed you"
3. suggestion-indexer: update mutual-friend graphs incrementally
4. analytics: stream follow and unfollow trends to ClickHouse
Sync path: validate, execute Cassandra dual write, update Redis, publish event, and return 201 Created (< 100ms p99).
Async path: feed fan-out and notifications tolerate up to 5 seconds of lag.
DLQ: follow-events-dlq, alerting when feed-fanout lag exceeds 30 seconds.Is-Following Lookup: 500K QPS Hot Path
Every profile visit, timeline load, and direct messaging authorization check evaluates follow status. The lookup follows a three-tier hierarchy:
- Redis Cache: Key
is_following:{a}:{b}returns in 1 ms with a 95% hit rate. - Cassandra Table: Performs an O(1) primary key lookup in approximately 5 ms on cache misses.
- Negative Caching: Writes
is_following:{a}:{b} = "0"with a 5-minute TTL to shield the database from repeated missing lookups.
Pipelined batch queries check 50 candidate connections in a single Redis MGET round trip when rendering mutual connections.
Celebrity Hot Partition: 100M Followers
Celebrity accounts apply compound partition keys: PRIMARY KEY ((user_id, bucket), follower_id) using bucket = hash(follower_id) % 100. This distributes 100M follower edges across 100 partitions of 1M rows each, ensuring stable compaction and read performance. Total counts update via atomic Redis INCR and DECR commands rather than executing expensive SELECT COUNT(*) queries.
Fan-out threshold: Accounts with over 100K followers switch dynamically to fan-out-on-read. Delivering a post to 100M inboxes via fan-out-on-write would require 100M Redis operations and ~6.4 GB of memory, which is operationally unsustainable. This hybrid architecture matches modern social network standards.
Unfollow, Block, and Consistency
Unfollow: Deletes rows from both tables, removes members from Redis sorted sets, and decrements counters atomically.
Block: Inserts into the blocks table, deletes existing follow relationships, and purges entries from both follower caches. The block check executes before every follow mutation.
Count Reconciliation: A scheduled Spark job aggregates edges per user, compares totals against Redis counters, and generates alerts if drift exceeds 1%. Remediation applies through administrative tooling rather than automatic overwrites to prevent masking counter bugs.
Follow Suggestions: Friends-of-Friends
Mutual connections calculate by gathering the following lists of users Alice follows, aggregating overlapping targets, ranking by shared frequency, and filtering out accounts already followed or blocked. An offline Spark job runs daily to populate the top 50 suggestions in Redis under suggestions:{uid}.
For celebrity profiles, the system intersects the viewing user's following list (~500 accounts) against the celebrity's followers using pipelined checks rather than materializing the full 100M-member set.
API Design
Follower and Following Endpoints
The API provides endpoints for follow and unfollow actions, cursor-paginated list queries, and fast relationship checks.
POST /api/v1/users/{target_uid}/follow
Authorization: Bearer <token>
Idempotency-Key: follow-{actor}-{target}-{timestamp}
Response: 201 Created
{ "following": true, "followee_id": "uuid", "created_at": "2026-03-14T10:00:00Z" }
Errors:
400 Bad Request: cannot follow yourself
401 Unauthorized: invalid or missing token
403 Forbidden: blocked by target user or rate limited
404 Not Found: target user does not exist
409 Conflict: already following (idempotent: returns 200 with same body)
429 Too Many Requests: follow rate limit exceeded (>100 follows/hour)
DELETE /api/v1/users/{target_uid}/follow
Response: 200 OK { "following": false }
GET /api/v1/users/{uid}/followers?cursor={last_id}&limit=50
Response: 200 OK
{ "followers": [{ "id", "name", "avatar" }], "total": 15234, "next_cursor": "..." }
GET /api/v1/users/{uid}/is-following/{target_uid}
Response: 200 OK { "following": true }Common Error Responses
Structured error schemas handle self-following, account block lists, and rate limit violations.
400 Bad Request: invalid input, missing required fields, or malformed JSON payload 401 Unauthorized: missing or invalid authentication token or API key 403 Forbidden: authenticated caller lacks required permissions for this resource 404 Not Found: requested resource ID does not exist 409 Conflict: duplicate write or version conflict, retry with a unique idempotency key 422 Unprocessable Entity: syntactically valid request failed semantic business validation 429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header 500 Internal Error: unexpected server failure, retry safely with an idempotency key 503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff
Data Model
Cassandra: Source of Truth
CREATE TABLE following (
user_id UUID,
follows UUID,
created_at TIMESTAMP,
PRIMARY KEY (user_id, created_at, follows)
) WITH CLUSTERING ORDER BY (created_at DESC);
CREATE TABLE followers (
user_id UUID,
bucket INT,
follower UUID,
created_at TIMESTAMP,
PRIMARY KEY ((user_id, bucket), created_at, follower)
) WITH CLUSTERING ORDER BY (created_at DESC);
CREATE TABLE is_following (
follower_id UUID,
followee_id UUID,
PRIMARY KEY (follower_id, followee_id)
);Cassandra provides horizontal write scalability across 200M daily events, bucketed partitioning for celebrity accounts, Last-Writer-Wins conflict resolution, and tunable consistency levels. For sharding foundations, review Sharding and Partitioning.
Redis: Cache Layer
following:{uid} -> Sorted Set (score=timestamp)
followers:{uid} -> Sorted Set (non-celebrity, < 100K)
is_following:{a}:{b} -> "1" (TTL: 24h)
follower_count:{uid} -> Integer
following_count:{uid} -> Integer
suggestions:{uid} -> List (pre-computed, TTL: 24h)Fault Tolerance
| Concern | Solution |
|---|---|
| Dual table write failure | Cassandra BATCH provides atomic partition writes with retry logic and idempotency deduplication. |
| Count drift | Hourly Spark reconciliation compares edge counts against Redis and alerts if discrepancy exceeds 1%. |
| Celebrity hot partition | 100-way bucket sharding paired with fan-out-on-read above 100K followers. |
| Follow spam and bot farms | Rate limit to 100 follows per hour with CAPTCHA challenges and graph cluster anomaly detection. |
| Redis failure | Fall back to Cassandra is_following table queries with asynchronous cache reconstruction. |
| Kafka follow event lag | Feed fan-out runs asynchronously with separate high-priority topics for critical notifications. |
| Follow and unfollow race condition | Apply Last-Writer-Wins timestamp semantics so unfollow actions always supersede concurrent retries. |
Additional Considerations
Mutual Friends Computation
For standard users, Redis ZINTERSTORE intersects follower sets with under 10K members. For celebrity profiles, the system intersects the viewer's following list (~500 accounts) using pipelined lookups against is_following:{friend}:celebrity.
Feed Fan-Out Integration
Standard users with under 10K followers utilize fan-out-on-write (pushing posts to follower inboxes), whereas celebrities with over 100K followers use fan-out-on-read (pulling posts at timeline load time). Review Caching Patterns for cache-aside nuances.
Related Problems
The Social Graph Store details the storage and traversal layer beneath this API, including dual adjacency tables, TAO cache architecture, and offline two-hop Spark jobs. In interviews, present the graph storage design if asked about low-level storage engines, or use this article when the prompt focuses on follow user experience and feed fan-out delivery.
Interview Walkthrough
- 25-minute pacing strategy
Focus on the dual-table model and celebrity fan-out thresholds before diving into recommendation algorithms.
- Draw the dual-table Cassandra model (following and followers) and explain the is-following hot path (5 min)
- Explain celebrity bucket sharding when a single follower list exceeds partition boundaries (6 min)
- Walk through the follow write sequence with idempotency keys and fan-out thresholds (~100K followers) (5 min)
- Contrast push vs pull feed fan-out for standard users vs celebrities (5 min)
- Staff depth: account blocking, rate limits, and eventual consistency on follower counts (4 min)
- Store edges as (follower_id, followee_id) pairs in Cassandra with indexes on both columns, because both follow and follower queries are hot paths.
- Cache paginated follower and following lists in Redis sorted sets ordered by follow timestamp, invalidating cache entries on mutation.
- Maintain denormalized counts in a Redis hash updated atomically on follow and unfollow actions, avoiding expensive COUNT(*) database scans.
- Apply hybrid feed fan-out: push fan-out on write for users under 10K followers, and pull fan-out on read for celebrities above 100K.
- Compute mutual friends via Redis ZINTERSTORE for standard users; for celebrity profiles, intersect the viewer's following list instead.
- Use the TAO pattern (Cassandra paired with Redis) rather than a graph database, because graph databases struggle past 500B edges at social network scale.
- Handle follow and unfollow races with Last-Writer-Wins timestamps so out-of-order packets do not leave stale follow states.
- Common pitfall: attempting pure write fan-out when a celebrity with 100M followers posts, because 100M inbox writes per post is structurally impossible.
Engineering Trade-offs
Architecture Trade-offs
Social follower systems balance counter denormalization against on-read aggregation, and push vs pull fan-out delivery.
Graph DB (Neo4j) vs Cassandra + Redis (TAO Pattern)
Single-hop queries complete fastest in Redis under 1 ms. Multi-hop friend-of-friend traversals run natively in graph databases but hit limits around 10B edges. Under high write throughput, Cassandra excels and scales past 200B+ edges. The TAO pattern remains the recommended choice for large-scale social networks.
Feed Fan-Out: Write vs Read
Write fan-out delivers posts instantly at O(followers) cost per post, whereas read fan-out pulls posts at feed render time at O(celebrities followed) cost per read. A threshold-based hybrid split provides the optimal balance at massive scale.
Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.