Start here: when do you pay for the fan-out?#
TL;DRthe 30-second version
- Fan-out is one write reaching many readers: a post to its followers' feeds, a change to every cache holding it, a notification to many devices. You either do that work at write time (push) or at read time (pull).
- Push (fan-out on write): when the event is written, copy it into every recipient's own precomputed place. Reads become one cheap lookup. But a single write now costs one operation per recipient.
- Pull (fan-out on read): store the event once. At read time, gather everything a reader should see and merge it. Writes are cheap. But every read redoes the fan-out work, so reads are slow.
- The trade is decided by two numbers: how many reads happen per write (more reads favours push, because you precompute once and read many times), and how skewed the fan-out is (one sender with millions of recipients breaks push).
- The production answer is almost always a hybrid: push for ordinary senders, pull for the few huge ones, and merge the two at read time.
Picture a social feed. Maya follows 200 accounts, and 800 people follow her. When Maya opens the app, she wants her home feed β the recent posts from those 200 accounts β to appear instantly. When Maya posts a photo, it needs to reach the feeds of her 800 followers. That is fan-out: her one post has to land in front of 800 readers.
There are only two moments when the system can do that spreading-out work. It can do it the instant Maya posts: take the photo and immediately write a copy of it into a per-user list for each of her 800 followers, so it is already sitting there when they open the app. Or it can wait until a follower actually opens the app: store the photo once, and when that follower reads, go find the accounts they follow and collect the recent posts right then. The first is fan-out on write, usually called push. The second is fan-out on read, usually called pull.
How each one actually works#
Push, step by step. Every user has their own home-feed list, stored somewhere fast to read β think of it as a precomputed inbox. When Maya posts, the system looks up her follower list (all 800), and for each follower it appends the post (or just its id) to that follower's feed list. By the time any of them opens the app, their feed is already built: the read is a single lookup of one list, in order, done. That precomputed list is really a cache of the answer β see the caching topics for the general pattern of storing a computed result so reads are cheap.
Pull, step by step. Now nobody has a prebuilt feed. When Maya posts, the system writes the post exactly once, into her own list of posts, and stops β no fan-out at all. The work moves to read time. When a follower opens the app, the system looks up the 200 accounts that follower follows, fetches the recent posts from each of those 200 lists, merges them into one time-ordered stream, and returns it. Every reader does this fan-out fresh, on every load.
The cost math (and the case that breaks push)#
Let F be the sender's fan-out (how many recipients a write reaches) and R be how many reads happen per write. Push pays F at write time and 1 per read, so its total work is roughly F plus R. Pull pays 1 at write time and F per read, so its total is roughly 1 plus R times F. Compare them: push wins whenever there is more than one read per write, because pull multiplies the fan-out cost by every single read while push pays it once and reuses it.
Then a celebrity signs up. Suppose @popstar has 50 million followers. Under push, one tweet is not one write β it is 50 million writes, all fired off from a single post. That is write amplification (many physical writes caused by one logical write) taken to an extreme. Worse, those writes are a burst: the moment @popstar posts, the system has to touch 50 million feed lists as fast as it can. And @popstar is a hot key β one account whose every action stampedes the same enormous follower set, concentrating load instead of spreading it. Much of that work is wasted, too: a huge fraction of those 50 million followers are inactive and will never open the app to read the post you just pushed to them.
Predict@popstar has 50 million followers and posts 5 times a day. Under pure push, how many feed writes does that one account generate per day β and what makes those writes especially painful beyond the raw count?
Hint: Multiply followers by posts, then think about timing (burst), concentration (hot key), and how many of those followers actually read.
50 million followers times 5 posts is 250 million feed writes a day, from a single account. The raw number is bad enough, but three things make it worse than 250 million evenly-spread writes. First, it is bursty: each post fires 50 million writes in a short window, a thundering herd that spikes load and delays the feed from ever being ready. Second, it is a hot key: the same giant follower set gets hammered on every post, so the load concentrates on one path instead of spreading out. Third, most of it is wasted: many of those 50 million followers are inactive and will never read the copy you pushed to them, so you spent write work building feeds nobody opens. This is the case that breaks pure push β not the average user, but the extreme fan-out sender.
Notice the asymmetry. Pull has the opposite failure. It never has a write burst, but a reader who follows many high-volume accounts pays a big merge on every load, and popular readers with huge follow lists make reads slow and expensive. So push is broken by senders with huge fan-out, and pull is strained by readers with huge follow lists and by read-heavy traffic. The two costs sit at opposite ends.
The hybrid: push for most, pull for the giants#
Once you see that push breaks only on the extreme senders and pull is fine for them, the fix writes itself. Do not pick one strategy for everyone. Push for ordinary accounts, whose fan-out is small enough that write-time copying is cheap. Do not push for the handful of celebrity accounts β leave their posts in one place and pull them at read time. Then, when a user loads their feed, merge the two sources: their precomputed feed (everything pushed to them by normal accounts they follow) plus a fresh pull of recent posts from the few celebrities they follow.
This gets the best of both. The heavy fan-out of celebrity posts never happens as a write burst, because those are pulled. And ordinary posts β the vast majority β are still precomputed, so the common read stays a cheap lookup with only a tiny extra pull on top. The threshold for who counts as a celebrity is just a tuning knob: pick a follower count above which pushing costs more than it is worth, and switch those accounts to pull. This split is what real feed systems actually run.
PredictA user follows 300 ordinary accounts and 3 celebrities. Under the hybrid, what does loading their feed cost β and why is that so much cheaper than pure pull for the same user?
Hint: Which of the 303 accounts pushed ahead of time, and which get fanned out at read time?
The 300 ordinary accounts have already pushed their posts into this user's precomputed feed, so reading those is one cheap lookup of a ready-made list β zero fan-out at read time. The only read-time fan-out is pulling recent posts from the 3 celebrities and merging them in, which is a tiny 3-way fetch. So the read cost is basically one lookup plus a 3-account pull. Pure pull for the same user would fan out across all 303 followed accounts on every single load β a 303-way fetch and merge each time. The hybrid moves the 300-account fan-out to write time (done once, by push) and leaves only the 3 accounts that were too expensive to push at read time. You pay read-time fan-out only for the few accounts where pushing would have been the bad deal.
Matching the strategy to the workload#
Feeds are just the famous example. The same fork shows up any time one event must reach many places, and the axes that decide it are always the same. Name the axis, name the trade you are accepting on it.
- Read-to-write ratio: the more reads per write, the more push pays off, because you precompute once and serve the result many times. A read-heavy workload leans push; a write-heavy one leans pull.
- Fan-out factor and its skew: a small, even fan-out is cheap to push. A skewed fan-out β a few senders reaching millions each β is what breaks push and forces pull (or a hybrid) for those senders. The skew matters more than the average.
- Latency SLO on reads: if reads must be fast and predictable, push wins, because a read is one lookup with no merge. Pull puts the fan-out on the read path, so read latency grows with how much you follow.
- Staleness tolerance: push makes reads instantly current but writes must finish fanning out; pull is always current at read time but slower. If a reader can tolerate a slightly stale precomputed view, push is easy; if not, you pay for freshness somewhere.
- Wasted work: push builds results for recipients who may never read them (inactive followers, caches never hit again). If a large share of recipients are inactive, that pushed work is wasted, and pull avoids it by doing work only when someone actually reads.
The honest answer is rarely push is faster or pull is faster. It is: name the dominant direction (are reads or writes more frequent), name the skew (is there a small set of senders with enormous fan-out), and pick the strategy whose bet matches β or split the two and hybridize. Because push writes are usually done asynchronously (the sender does not wait for all 800 copies to land), fan-out on write is also a classic job for a message queue: enqueue the post, and workers fan it out in the background. See the queue and pub/sub topics for that delivery machinery.
The decision, in one table#
| Push (fan-out on write) | Pull (fan-out on read) | |
|---|---|---|
| When the work happens | At write time, eagerly | At read time, on demand |
| Write cost | High β one write per recipient, so it grows with fan-out | Low β store the event once |
| Read cost | Low β one lookup of a ready-made list | High β fetch and merge from many sources |
| Storage | High β a copy per recipient | Low β one copy, read by all |
| Breaks when | A sender has huge fan-out (celebrity) | A reader follows many, or reads are frequent |
| Best at | Read-heavy, small/even fan-out, tight read SLO | Write-heavy, skewed fan-out, staleness OK |
| If your workload is⦠| Lean toward | Because |
|---|---|---|
| Read-heavy, reads must be fast | Push | Precompute once, serve many cheap lookups |
| Write-heavy, reads rare | Pull | Do the work only when someone actually reads |
| A few senders with enormous fan-out | Pull (for them) | Avoid the write burst and wasted copies |
| Most senders small, a few huge | Hybrid | Push the many, pull the giants, merge at read |
| Many recipients are inactive | Pull | Don't build results nobody reads |
When in doubt: read-bound with even fan-out reaches for push; write-bound or skewed fan-out reaches for pull; and any real system at scale reaches for the hybrid, because real fan-out is almost always skewed.
In the wild
- Social feeds: Twitter's timeline famously moved to a hybrid β precompute (push) most users' home timelines, but pull posts from very-high-follower accounts at read time and merge them in, exactly to dodge the celebrity write-burst.
- Notifications: pushing a notification into every device's queue the moment an event happens is fan-out on write; letting a device ask what's new when it connects is fan-out on read. Systems pick per event type based on urgency and how many devices are active.
- Cache invalidation: when a value changes, you can push the new value (or an invalidation) to every cache holding it, or let each cache pull a fresh copy when it next reads (and finds its entry stale or expired). Push keeps caches current but fans out to every replica on each change; pull keeps writes cheap but serves briefly-stale data. This is the exact push-vs-pull trade applied to caches β see the cache-strategy topic.
- Materialized views: a materialized view is a precomputed query result kept up to date as the base data changes β push (fan-out on write) applied to a database query. A plain view computed on demand is pull. Same fork: precompute the result eagerly, or recompute it on read.
- Chat and group messaging: delivering a message into each member's inbox as it is sent is push; a client fetching a room's recent messages on open is pull. Large groups (the celebrity case again) push maintainers toward pulling for the biggest rooms.
Pitfalls & gotchas
Isn't push just strictly better because reads are fast, and reads are what users see?
Only until a sender has huge fan-out. Push turns one write into one operation per recipient, so a celebrity post becomes tens of millions of writes in a burst β a hot key that spikes load, delays the feed, and wastes work on inactive followers who never read. For ordinary accounts push is great; for the extreme senders it falls over. That's precisely why real systems pull for the giants instead of pushing.
If pull keeps writes cheap and is always current, why not pull everything?
Because pull puts the entire fan-out on the read path, and reads usually outnumber writes by a lot. A read-heavy feed pulling from hundreds of accounts on every load repeats an expensive merge again and again, and read latency grows with how much a user follows. Precomputing with push and serving cheap lookups is the win exactly when reads dominate β which is the common case for feeds.
What is write amplification here, and how is it different from a cache?
Write amplification is one logical write causing many physical writes β a post fanning out into a copy per follower. It's the cost push pays. A precomputed feed is a cache of the fan-out result, so push is caching applied to fan-out; the amplification is the price of building that cache eagerly at write time instead of lazily at read time.
Under push, does the sender wait for all the copies to be written?
Usually not. The fan-out is done asynchronously: the post is written once, then handed to background workers (typically via a message queue) that append it to each follower's feed. The sender's write returns immediately; the copies land shortly after. That's why a pushed feed can be briefly incomplete right after a post, and why fan-out on write is a textbook queue workload.
QuizA team runs a read-heavy social feed. Most accounts have a few hundred followers, but a small number of accounts have tens of millions. They want fast, predictable feed reads without a write meltdown when a big account posts. What should they do?
- Pure push for everyone β reads stay fast and that's what matters.
- Pure pull for everyone β writes stay cheap and there's no fan-out burst.
- Hybrid β push for the ordinary accounts, pull for the huge accounts, and merge both at read time.
- Pick push or pull globally based on a coin flip; the two are equivalent at scale.
Show answer
Hybrid β push for the ordinary accounts, pull for the huge accounts, and merge both at read time. β The hybrid is the right call. Reads are heavy, so most of the feed should be precomputed with push for fast, predictable lookups β that's the common case and push nails it. But pure push would melt down when a tens-of-millions-follower account posts, firing a huge write burst to mostly-inactive followers. So those few giant accounts are left unpushed and pulled at read time, then merged into each user's precomputed feed. Pure push breaks on the celebrities; pure pull makes the read-heavy common case slow; the hybrid pushes the many and pulls the few, which is exactly what production feeds do.
In an interview
Fan-out is one of the most common feed-design questions, and it rewards framing over a memorized answer. Don't declare push or pull the winner β name the fork, name the two numbers that decide it, and then reach for the hybrid. That's the difference between reciting a fact and showing you can choose.
- State the fork first: do the fan-out at write time (push β cheap reads, write cost per recipient) or at read time (pull β cheap writes, read does the fan-out). Everything follows from that.
- Name the two deciders: the read-to-write ratio (more reads favours push) and the fan-out skew (a few senders with huge reach breaks push). The skew is the one candidates forget.
- Do the celebrity math out loud: 50M followers times one post is 50M writes, a burst, on a hot key, much of it wasted on inactive followers. That number is why pure push fails.
- Reach for the hybrid: push for ordinary accounts, pull for the giants, merge at read time β and mention the follower-count threshold as the tuning knob. This is the answer real systems use.
- Generalize past feeds: same fork in cache invalidation (push the update vs pull on read), notifications, and materialized views. Naming that push writes are async via a queue shows you know the delivery machinery too.
PredictAn interviewer says: 'You designed a pure-push feed and it works great in testing. In production it falls over whenever a popular account posts. What happened, and what do you change?' What's the strong answer?
Hint: What was different about the accounts in testing versus production, and does that mean push is wrong for everyone β or just for some?
In testing every account had a small, even follower count, so push was cheap and fast. Production has skew: a few accounts have millions of followers, and under pure push each of their posts fires millions of feed writes at once β a burst on a hot key that spikes load, delays feeds, and wastes work on inactive followers. The fix is not to abandon push, which is still right for the ordinary majority; it's to go hybrid. Set a follower-count threshold: below it, keep pushing; above it, stop pushing and instead pull those accounts' posts at read time, merging them into each user's precomputed feed. That removes the write burst (the giants are pulled) while keeping the common read a cheap lookup (the many are still pushed). The strong move is diagnosing skew as the cause and splitting the strategy by sender size rather than flipping the whole system to pull.
References
- Designing Data-Intensive Applications, Ch. 1 (Twitter timelines) β Kleppmann uses the fan-out-on-write vs fan-out-on-read feed example, including the celebrity hybrid.
- The Infrastructure Behind Twitter: Scale β Twitter engineering on timeline fan-out and precomputed home timelines.
- Facebook TAO: The Associations and Objects graph store β How a read-heavy social graph is served β the read/write asymmetry that pushes fan-out decisions.
- Caching strategies (read-through, write-through, write-back) β Push vs pull applied to caches: propagate a change eagerly, or refresh on read.
- PostgreSQL β Materialized Views β A precomputed query result (push) versus a view computed on demand (pull).
Ready to try it?
The simulator is a real, deterministic implementation β pick a scenario and step through it, scrubbing the timeline forward and backward through every change.