The system design question
Design a feed like Facebook's where millions of users can post text, images and videos and see posts from the people they follow. Like and view counts should update in real time, and the feed should load almost instantly, even for someone who's just cleared their cache.
Agree on the scope (5 minutes)
Say up front what you're covering.
| We'll design | We'll skip |
|---|---|
| Creating posts with text, images and video | Ranking with machine learning |
| Building each person's feed | Comments and sharing |
| Likes and view counts | Ads |
| Loading the feed fast | Moderation |
Then agree on what "good" means:
- Fast: the first screen of the feed shows up in under about 200 ms, cold cache or not.
- Fresh enough: a new post shows up in followers' feeds within a few seconds.
- Live counts: like and view counts update within a second or two, and they can be a little off for a moment.
- Handles celebrities: someone with 50 million followers posting doesn't slow anything down.
Rough numbers (5 minutes)
These are assumptions, but reasonable ones: 500 million people use it a day, each opens the feed about 10 times, 100 million posts a day, an average of 200 followers each, and 10 likes per person per day.
- ~60K
- feed loads per second, on average
- ~1.2K
- new posts per second
- ~230K
- feed inserts per second if we push every post
- ~20 TB
- of new media a day
| Question | Rough math | What it means |
|---|---|---|
| How many feed loads? | 500 million × 10 a day ÷ 86,400 seconds | About 60K a second, maybe 150K at a peak. Reads are what we optimize |
| How much work to push each post to followers? | 100 million posts × 200 followers ÷ 86,400 | About 230K small writes a second. Big, but fine if each is tiny |
| How hot can one like counter get? | 1 million likes in the first minute of a celebrity post | About 17K writes a second to one number, too many for a single row |
| How much media? | 10 million posts with media × about 2 MB | About 20 TB a day, so it goes in object storage behind a CDN |
The overall design (10 minutes)
- The app uploads any image or video straight to object storage, then creates the post with text and media links. The post goes into the posts database.
- Saving the post puts an event on a queue. Fan-out workers look up the author's followers and add the post ID to the top of each follower's feed list in a cache.
- When you open the app, the feed service reads your list of post IDs, adds recent posts from any celebrities you follow, and fetches the post details in one batch.
- Images and video come from a CDN, so the feed service only sends small JSON and media links.
- Likes and views go to a counter service that adds them up in memory and writes totals every second or so. Open feeds get count updates pushed over a live connection.
What the apps need to do
| Action | Who does it | What's sent |
|---|---|---|
| Get an upload link | App | Media type and size. Returns a link to upload straight to storage |
| Create a post | App | Text, media IDs and a request ID so retries don't post twice |
| Get my feed | App | A cursor from the last page, and how many posts |
| Like or unlike | App | Post ID |
| Record views | App | A batch of post IDs seen in the last few seconds |
| Follow or unfollow | App | The other user's ID |
What we store and where
| Data | Where it goes | Why |
|---|---|---|
| Posts | Key-value database, split by post ID | Huge and simple to look up by ID |
| Who follows whom | Database, split by user, stored both ways | Fan-out needs followers, and the read side needs who you follow |
| Feed lists | In-memory cache, split by user | Last ~500 post IDs per person, rebuilt if lost |
| Media | Object storage behind a CDN | Big files, read from everywhere |
| Like and view counts | Key-value database, plus in-memory counters | Totals saved every second or so |
| Who liked what | Database, split by post | So the same person can't like twice |
Step 1: Building feeds: push, pull, or both (12 minutes)
There are two basic options. Push means when you post, we add the post ID to every follower's feed right away. Reading is then one quick lookup. Pull means we do nothing on post, and when you open the app we fetch recent posts from everyone you follow and merge them. Reads get slow if you follow hundreds of people.
Push is better for us because reads outnumber posts by about 50 to 1. Feed lists live in a cache split by user ID, a lot like sharding any big dataset. Each list only holds post IDs and timestamps, so 500 IDs is a few KB per person.
Push breaks for celebrities. One post from someone with 50 million followers is 50 million writes, and they'd land seconds or minutes late. So we mix the two. Above some follower count, say 100K, we don't push. When you open your feed, we read your pushed list and also pull recent posts from the handful of celebrities you follow, then merge by time.
Fan-out runs off a queue, so a burst of posts just makes the queue longer for a bit. Workers can also skip people who haven't opened the app in a month. Their feed gets rebuilt by pulling when they come back.
Step 2: Likes and views at celebrity scale (10 minutes)
If every like does 'add one' on a single database row, a viral post means 17K writes a second to one row, and everything waits on a lock. So don't write each like straight through.
Likes go on a queue split by post ID. Counter workers keep a running total in memory per post and write it every second or so. That's basic stream processing: one write a second per post instead of thousands. For the very hottest posts, split the count across several sub-counters and add them up when reading.
We still need to stop someone liking the same post twice, so we save who liked what, keyed by post and user. Saving the same pair twice is harmless, which makes retries safe. That's idempotency.
Views are even more frequent, and nobody needs them exact. The app batches them and sends them every few seconds. Counts on screen can be a second or two behind, which is the kind of consistency trade we agreed to up front.
Getting counts to open screens
Each open app keeps a live connection to a gateway, and tells it which posts are on screen. When a post's total changes, we send the new number to the gateways watching it, at most once a second per post. That keeps a viral post from flooding everyone.
Step 3: Loading fast with an empty cache (8 minutes)
'Cleared their cache' means the phone has nothing saved, so the server has to do all the work in one round trip. The feed service reads your feed list, then fetches all the post details in one batched read, and returns the first 10 or 20 posts. Counts and author names come from their own caches in the same step.
Media is the slow part. Send small thumbnails and low-quality preview images first, and let the app load full images and video from the CDN as you scroll. Videos are stored in a few sizes so phones on bad connections get the small one.
On our side, if your feed list isn't in the cache, maybe because you've been away, we rebuild it by pulling from who you follow. That's slower, maybe 300 ms, but only on the first load. We keep the cache's hit rate high by keeping lists for everyone active in the last few weeks.
Choices you should be able to defend
| We chose | Instead of | Because | We'd reconsider if |
|---|---|---|---|
| Push for most people, pull for celebrities | Push for everyone | Celebrity posts would mean millions of writes each | Nobody had a huge following |
| Counts added up in memory, written every second | One database write per like | One hot post would hammer a single row | Traffic was low enough that rows never got hot |
| Counts can be a second or two behind | Exact counts at all times | People don't notice, and exact would cost a lot | Counts drove something like payments |
| Feed lists hold IDs only | Full posts copied into every feed | Edits and deletes only touch one place, and lists stay tiny | Fetching post details became the slow step |
What could go wrong (5 minutes)
| What happens | What we do about it |
|---|---|
| The feed cache loses a machine | Those users get feeds rebuilt by pulling, a bit slower, until the cache fills back up |
| Fan-out falls behind | Posts show up late in feeds, but nothing's lost. Add workers |
| A counter worker crashes | Restart from the last saved total and replay the queue from there |
| A post goes viral | Its count is split across sub-counters, and screen updates stay at once a second |
| The CDN is slow in one region | Send other image sizes or fall back to a second CDN |
Follow-up questions (5 minutes)
How would you add ranking instead of newest first?
Pull a few hundred candidate posts from the feed list, score them with a model using things like how often you interact with the author, and sort. The feed list stays the same.
What happens when someone deletes a post?
Mark it deleted in the posts database. Feed lists still hold the ID, but the feed service skips deleted posts when it fetches details.
What if someone follows a new person?
Pull that person's last few posts into your feed list right away, so it doesn't feel empty.
How the 60 minutes add up
| Minutes | What you're doing |
|---|---|
| 0–5 | Agree on what you're building |
| 5–10 | Rough numbers |
| 10–20 | Draw the overall design |
| 20–32 | Building feeds: push, pull, or both |
| 32–42 | Likes and views at celebrity scale |
| 42–50 | Loading fast with an empty cache |
| 50–55 | What could go wrong |
| 55–60 | Follow-up questions |
Key concepts used here
- Caching strategiesMake reads faster, and figure out what to do when data changes.
- Message queues and event streamsKeep services from depending on each other, and handle traffic spikes.
- Sharding and consistent hashingSplit data across machines and keep it balanced.
- Stream processing and windowingCount things in data that never stops coming in.
- Consistency and the CAP theoremWhat happens when copies of your data can't agree.
- Idempotency and retriesMake sure a retried request doesn't happen twice.
- Real-time deliveryPush updates to users the moment they happen, with WebSockets and friends.
- Blob storage and CDNsStore big files cheaply and serve them from close to your users.
Further reading
We wrote this sample answer ourselves.
Practice Loop is independent and isn't affiliated with or endorsed by Amazon.