Social Network
Post something, and everyone who follows you should see it without refreshing into a full table scan. The whole design question is: who does the work of getting a post into a follower's feed, and when? Doing it at read time is simple and slow; doing it at write time is the interesting part.
Requirements
Functional
- A user follows or unfollows another user.
- A user publishes a text post, visible to their followers.
- Each user has a feed: posts from people they follow, newest first.
- A follower is notified the moment someone they follow publishes.
Non-functional
- Reading a feed must not require scanning every post ever made by every followed user - the fan-out work happens once, at publish time, not on every read.
- Adding a new way to be notified (push, email, in-app banner) must not require changing
UserorPost.
Design
Post doesn't know who its followers are and User doesn't loop over anyone else's data to
build a feed - publishing a post notifies a list of subscribed observers, and each observer
decides what to do with the news. That's Observer doing the one thing it's actually good
for: decoupling "something happened" from "here's what happens next."
- 1The author only ever talks to the network facade, never to a follower directly.
- 2A post is created once and handed its author’s current follower list.
- 3Publishing is the post’s own job - it owns the notify step, not the network.
- 4Every follower’s Feed is an observer; the post has no idea Feed exists beyond this interface.
- 5The feed prepends the post and trims itself to its cap - nobody outside Feed manages its size.
Feed is one such observer: it just prepends the post to a capped list. A push-notification
service could be a second observer on the exact same publish call, with zero changes to
SocialNetwork or Post.
Class diagram
Code
Design decisions
- Fan-out happens on write, not on read.
SocialNetwork.publishpushes the new post into every follower'sFeedimmediately. Building a feed at read time by merging every followed user's post history would be simpler to write and unusable at any real follower count - this is the classic push-vs-pull tradeoff, and this page takes the side that keeps reads cheap. - Notification is Observer, not a hardcoded call to
Feed.prepend.Post.publishnotifies a list ofFeedObservers;Feedhappens to be the only implementation here, but a second observer (an email digest, a push notification) plugs in at the same call site with no change toUser,Post, orSocialNetwork. Feedcaps its own size instead of trusting callers to trim it. A feed that grows forever turns "prepend a post" into a slow operation years into a user's life; keeping the cap insideFeed.receivemeans every caller gets the same guarantee for free.- Follow/unfollow is a set operation on
User, not aFriendshipjoin object. A follow here is one-directional and has no state of its own (no pending/accepted), so a plainSet<User>of followers is the whole model - introducing a relationship class would be structure with nothing to hold. - What's missing for a real system: ranking (chronological only here, no relevance
scoring), pagination past the in-memory cap, and de-duplicating a post that reaches a user
through more than one path (retweets/shares) are all real feed-system concerns that don't
change the observer wiring above - they'd sit inside
Feed.receive, not around it.
Common follow-ups
- How would you add email-digest notifications alongside the in-app feed? Write a new
FeedObserverimplementation (EmailDigestObserver) and add it to the observers listSocialNetwork.publishbuilds -Post,User, andFeeddon't change at all, sincePost.publish()only ever knows it's notifying a list ofFeedObservers. - What happens to fan-out cost for a celebrity account with millions of followers?
Fan-out-on-write becomes expensive exactly there - a real system splits into a hybrid: fan
out normally for most users, but switch high-follower accounts to fan-out-on-read (merge
their posts into a follower's feed only when that follower opens it) - a change to
publish's strategy, not to theFeed/FeedObservercontract. - How would unfollowing remove that user's posts from an already-built feed?
Feedwould need to track which author each stored post came from, thenrecent()(orreceive) would filter against the caller's current-follow set - today'sFeedonly stores posts, not who they're from, so this needs a new field, not new wiring. - How do you rank a feed by relevance instead of strict recency? Swap
Feed.receive's prepend-and-trim logic for an insert that respects a score instead of chronological order- since ranking was explicitly named as out of scope, this is a real change to
Feeditself, not to the Observer plumbing around it.
- since ranking was explicitly named as out of scope, this is a real change to
Check yourself
Why does Post notify a list of FeedObservers instead of calling Feed.prepend() directly?