One write, many recipients
When a user posts something in a social system, every one of their followers needs to see it. At 100 followers, this is trivially solved — just write 100 records. At 100 million followers (a celebrity account), writing 100 million records synchronously before returning to the user would take hours. The system would be unusable.
This is the fan-out problem: one event that must propagate to many targets. The three approaches — push, pull, and hybrid — are fundamentally different tradeoffs between write-time complexity and read-time complexity. You can't optimize both simultaneously. Whatever you don't pay on the write path, you pay on the read path.
Every fan-out solution other than synchronous push depends on the system tolerating a delay between when something is written and when all recipients see it. In a news feed, 3–5 seconds is acceptable. In a stock trading system, it isn't. Always confirm the consistency requirement before choosing a fan-out strategy. The acceptable lag is what determines which approaches are on the table.
Fan-out on Write — Push
When a user posts, immediately write a record into every follower's feed table. By the time any follower loads their feed, their posts are already pre-computed and waiting. Reading the feed is trivially fast — just query by userId and return pre-sorted records.
- Feed reads are instant — pre-computed
- Read path is simple: query by userId, return results
- No computation at read time
- Works well for users with small follower counts
- Write amplification: one post → N writes (one per follower)
- Celebrity post → millions of writes simultaneously
- Feed table grows proportionally to followers × posts
- Inactive followers still receive writes they may never read
Push-only works well when the follower graph is sparse and relatively balanced — no single user has dramatically more followers than the average. It breaks under celebrity load because one post triggers unbounded write amplification. A Kafka queue can make the push async (post service returns immediately, fan-out happens in background), but the total write volume is unchanged.
Fan-out on Read — Pull
Don't pre-compute anything at write time. When a user loads their feed, fetch posts in real time from everyone they follow, merge them, sort them, and return the result. Write path is trivially simple — just write the post to the post table. The read path is where all the work happens.
- Write path is simple: one write to post table
- No write amplification — celebrity posts are free to write
- Feed is always up-to-date — no staleness
- No storage for pre-computed feeds
- Read path is expensive: N queries for N followees
- If user follows 1,000 people: 1,000 lookups on every feed load
- 5,800 reads/sec × 1,000 queries each = 5.8M DB queries/sec
- Merge and sort at read time adds latency
Pull-only works when the follow graph is dense but the number of followees per user is small. It breaks at scale because the read-time computation — N lookups + merge + sort — grows with the number of followees. At Facebook scale where users follow hundreds of people, this is functionally impossible at acceptable latency.
Hybrid — Push for regular, Pull for celebrities
The hybrid approach recognizes that the push and pull models have complementary failure modes: push fails at high follower counts (celebrity writes), pull fails at high followee counts (expensive reads). Apply each where the other fails.
The threshold is the key decision. In the News Feed session, 1M followers was chosen as the cutoff. Below 1M — fan-out service pushes to all eligible followers' feed tables. Above 1M — skip fan-out entirely. On the read path, the get service identifies which followees are celebrities (flagged in user table), fetches their recent posts directly from post cache via the hybrid get service, and merges with the pre-computed regular friend feed.
Celebrity posts get millions of read requests — they will always be at the top of the post cache's LRU eviction list. Pulling them on the read path is not expensive in practice: one cache hit per celebrity per feed load. If the celebrity had 100 followers, their post wouldn't be cache-hot and pull would be expensive. The inversion only works because celebrity posts are always cached. The same logic does not apply to a user with 1M followers who posts rarely — their posts may not be cache-hot, and pulling them would be slow.
The 1M threshold is a starting point, not a law. The right threshold is the point at which fan-out volume becomes a capacity problem for your fan-out service. At 58 write QPS and an average of 500 followers, fan-out handles ~29,000 feed writes per second — manageable. A single user with 100M followers posting would add 100M writes instantly. The threshold keeps any single post from dominating fan-out capacity. Tune it based on fan-out service throughput and acceptable lag.
Inactive follower filtering
Even below the celebrity threshold, not all followers should receive fan-out writes. A user who hasn't opened the app in 30 days doesn't need their feed pre-populated — they'll get a fresh pull when they return. Skipping inactive users reduces fan-out volume significantly, because inactive users often represent a large fraction of total followers.
The fan-out service checks the is_active flag on each follower before writing to the feed table. The analytics service maintains this flag — updated from app open events. When an inactive user returns, their first feed load is a pull (slightly slower) but subsequent loads benefit from push once they're marked active again.
Studies of social platforms consistently show that a large fraction of registered users are inactive at any given time — sometimes 40–60%. If your fan-out service is writing to inactive users' feed tables, it's doing 40–60% more work than necessary. At scale, that's the difference between a manageable fan-out service and one that's permanently overloaded. Inactive follower filtering is a real production optimization that most textbook answers miss.
Comparison table
| Dimension | Push (write) | Pull (read) | Hybrid ✓ |
|---|---|---|---|
| Write cost | High (N writes) | Low (1 write) | Medium (N writes for non-celebrity) |
| Read cost | Low (pre-computed) | High (N lookups at read time) | Low (pre-computed + cache pull) |
| Celebrity handling | Breaks (100M writes) | Fine (1 write) | Pull model above threshold |
| Inactive users | Wasteful writes | No waste | Filter inactive from push |
| Feed freshness | Always current | Always current | 3-5s eventual consistency |
| Complexity | Low | Low | High (two paths, threshold logic) |