DEV Community

Cover image for Fan-out on read vs fan-out on write: choosing the right trade-off for a dynamic feed
Nana Axel
Nana Axel

Posted on AI-assisted

Fan-out on read vs fan-out on write: choosing the right trade-off for a dynamic feed

This post will talk about how every feed in our social medias are designed. From the user’s side, a feed looks simple: open a page, see recent updates, keep scrolling, infinitely.

But behind that list, there is an improtant design decision: when should the system assemble each person’s feed?
It can do most of the work when someone publishes a post, or wait until a reader opens their feed. Those approaches are usually called fan-out on write and fan-out on read.

The right choice is not picking one, as each moves work to a different part of the system, and the better fit depends on how often people post, how often feeds are read, and how unevenly followers are distributed.

First, what is being fanned out?

Let's say Alice publishes a post and 200 people follow her. The system needs to make that post available in those 200 people’s feeds. It could:

  • add a reference to Alice’s post to each follower’s feed when she publishes it; or
  • leave the post in Alice’s own collection and find it when each follower opens their feed.

The post itself remains in one canonical record either way. A precomputed feed often stores post IDs or references, rather than a full duplicate of every post. So the design question is not whether a feed needs aggregation (it always needs it), it is when that aggregation happens.

Fan-out on write: prepare the feed ahead of time

Fan-out on write needs a prerequisite: each user has its own feed collection, referencing followed users' posts. That feed is persisted in the storage that suits most your use case (database, cache, memory). When a user publishes a post, the system creates new feed entries for its followers:

publish(post):
    storeCanonicalPost(post)
    for follower in followersOf(post.author):
        addReferenceToFeed(follower, post.id)
Enter fullscreen mode Exit fullscreen mode

When a reader opens their feed, the system retrieves a list that is already assembled. That makes the read path easier to keep fast and predictable. This method is optimal when people read feeds frequently and most authors have a manageable amount of followers.

On the other hand, the cost moves to publishing. One post by an account with 200 followers may create around 200 feed entries, same for a post by an account with millions of followers. It also do work for followers who rarely open the feed. And when a post is deleted or access changes, every stored reference may need to be removed or checked against current permissions.

In real production systems, those issues are mitigated by making the fan-out asynchronous. The post is stored first, then background workers create references to followers’ feeds. This keeps a large delivery job off the request that publishes the post, with the cost of a briefly outdated feed.

Fan-out on read: assemble the feed when it is requested

With fan-out on read, publishing stores the post once. When someone opens their feed, the system fetches recent posts from the accounts they follow, then merges and ranks the results:

getFeed(reader):
    authors = followedAuthors(reader)
    candidates = fetchRecentPosts(authors)
    return rankAndLimit(candidates)
Enter fullscreen mode Exit fullscreen mode

This avoids all the costs explored in fan-out on write approach. It can be a good fit when writes are frequent, many followers are inactive, or ranking rules change often.

But the cost is non-negligible, since the work appears on the read path instead. A reader who follows many accounts may require the system to fetch and combine a large set of recent posts for each request. That can increase database load and make latency less predictable.

The implementation matters here. “One query per followed account” is not the only possible design; indexes, batching, caching, and the way data is partitioned all affect the cost. But the system still has to gather enough candidate posts to build a useful feed at request time.

The trade-off

Fan-out on write Fan-out on read
Main work happens When a post is published When a feed is requested
Publish cost Grows with follower count Usually stays close to one canonical write
Feed read Starts from a prepared list Must gather and combine candidates
Storage More feed references to maintain Fewer precomputed feed entries
Useful when Reads are frequent and audiences are manageable Writes are frequent, audiences are uneven, or ranking changes often
Main pressure Large authors create write spikes Large follow lists make reads more expensive

This table describes where the work moves. It doesn’t guarantee a particular latency or storage cost. Those depend on the data model, indexes, caching, ranking, and delivery requirements.

Why many systems use a hybrid

Follower counts tend to be uneven. Most authors may have a modest audience, while a small number have very large ones. That makes a hybrid practical:

  • Fan out posts from ordinary accounts on write.
  • Keep posts from very large accounts in their own collections.
  • Fetch those high-follower posts when a reader opens their feed.
  • Merge both sets of candidates before returning the feed.

The hybrid approach avoids pushing every post to a huge audience immediately, while also avoiding a read-time fetch from every single followed account. There is no universal follower-count threshold. The useful cutoff depends on your workload: how frequently authors post, how often their followers read, how much read latency you can afford, and how much asynchronous write work your system can absorb.

Ask questions before choosing

  1. What is the read-to-write ratio? If feeds are opened far more often than posts are published, precomputing some work may pay off.
  2. How skewed are follower counts? A few exceptionally large accounts can make a pure write-time strategy costly.
  3. How fresh must the feed be? Asynchronous fan-out can introduce a short delay; this is typically accpetable for large feeds.
  4. How often does ranking change? If feed order depends on changing relevance scores, rebuilding stored feeds can become part of the cost.
  5. How do deletes and permissions work? This can be tricky, since a feed entry should not make content visible after the underlying post or access rights have changed.

I recently worked on a project where such decisions were required, since the project is new an not yet extensively used, we selected fan-out on read while we collect enough metrics to evaluate when and how to optimize: measure where the work happens, then optimize the bottleneck the product actually has.

What would you choose for a feed where most accounts have a small audience, but a few have millions of followers? Let's discuss in comments!

Top comments (0)