Inkwell Home is the page every reader opens first: the newest posts from the writers they follow. Design it the way an interviewer would ask you to, in order: ask what it must do, work out the numbers, pick a design, and say what each choice costs. The deep dives name the chapters they lean on.
Ask before you draw
Start with questions, not boxes. The answers decide the design, and asking them shows you know which ones matter.
| Question | Why it matters | Inkwell Home |
|---|---|---|
| What does a reader see? | Newest first, or ranked? Ranking is its own system | Newest first |
| How fresh must it be? | Seconds of delay allows work to be done ahead of time | A new post within a few seconds |
| How fast must it open? | Sets the latency budget for every read | 99% of feeds in under 60 ms |
| How many follow whom? | Decides how much one post costs | Most writers have a few dozen followers, a few have millions |
| What can be wrong? | A feed missing one post is fine, a lost post is not | Late is acceptable, lost is not |
Write the answers down where the interviewer can see them. The rest of the interview comes back to them.
Estimate the load
Work the chain yourself first, from the assumptions in the worksheet. The worked numbers open once you've done it.
The simulator runs a scaled-down version of this: 1 000 requests a second, 100 000 feeds, 30 followers for most writers and 2 000 for the 1% with the biggest audiences.
API and data model
Two calls cover it: GET /feed?cursor=... returns a page of the newest posts and a cursor for the next page, and POST /posts publishes one. Page with a cursor, not an offset, so a feed that gains a post while you scroll doesn't repeat or skip one.
Three kinds of data hold it together:
- Posts: one row per post (id, author, time, text), keyed by post id.
- Follows: who follows whom, kept both ways so you can find a writer's followers and a reader's follows.
- Feeds: one list per reader of recent post ids, keyed by reader.
The first two live in the database, because they're the record. The feed is a copy that can always be rebuilt from them, so it can sit in a fast key-value store, split by reader so each feed is one lookup.
Three ways to build a feed
Pull: build the feed when it's opened. Look up everyone the reader follows, fetch their latest posts and merge them. Posts are written once, and every read does the gathering.
Pull: cheap to write, and every read pays for the gathering.
Push: build the feed when a post is written. Copy the post into the feed of each follower, and a read becomes one lookup.
Push: a read is one lookup, and every post is a write per follower.
The celebrity problem
Most writers have a few dozen followers, so pushing their posts is cheap. A few have millions, and for them push is a disaster while pull is easy: one post is read by many, so it's a hot key a cache handles well. The standard answer is a hybrid: push for ordinary writers, and fetch the biggest writers' posts when a feed is read.
Hybrid: each read looks in two places, and a celebrity's post is never copied.
The line is a follower count: a celebrity is any writer above it. Celebrities' posts go to the cache and the posts database, and are fetched when a feed is read. Everyone else's go to the queue, and workers copy them into feeds. In the simulation the writers with the most followers have the lowest numbers, so there the line is a writer number, and the writers below it are the celebrities.
Deep dives
Interviewers pick one or two of these after the main design. Each leans on a chapter you've seen.
| They ask | What to say | Chapter |
|---|---|---|
| The feed store fails | Rebuild feeds from the posts and the follows, serve a pulled feed meanwhile | Availability |
| A reader doesn't see their own post | Read-your-writes: route their reads to the posts database for a moment | Consistency |
| The queue backs up | Watch the age of the oldest job, add workers, and if copies expire, pull that reader's feed when it's opened | Pub/sub, Queues and workers |
| A celebrity posts and the cache is cold | Warm it on publish, and let one request fill it | Hot keys, Caching |
| A post is copied twice | Make the copy idempotent: the same post id to the same feed changes nothing | Idempotency |
| A reader unfollows | Remove their posts lazily when the feed is read | Fan-out |
You won't have time for all of these. Say which you'd cover first and why.
Say the tradeoffs
End by comparing the options on the axes you started with. There's no best design, only one that fits the answers you wrote down.
| Pull | Push | Hybrid | |
|---|---|---|---|
| Opening a feed | Slow: gathers every time | Fast: one lookup | Fast: a lookup and a cached fetch |
| Cost of a post | One write | A write per follower | Per follower, except celebrities |
| Freshness | Always current | Delayed by the queue | Delayed for most, current for celebrities |
| Cost to run | Lowest here, highest at scale: every open gathers | High: a copy per follower, made once per post | In between |
| Complexity | Lowest | A queue and workers | A queue, workers and a rule to maintain |
Then commit: hybrid, with the line set so the workers just keep up, because feed opens outnumber posts and a few writers account for most of the copying. Say what you'd watch: the feed delay, the age of the oldest job and the p99 of a feed open.
In the wild
The ideas in this chapter, and where you'll meet them in real systems and interviews:
| Idea | Where you'll meet it |
|---|---|
| Precomputed per-reader feeds | Twitter's home timeline, kept in Redis lists and described in its engineering talks |
| Hybrid push and pull | Twitter treating accounts with very many followers differently from the rest |
| A key-value store for feeds | DynamoDB or Cassandra tables keyed by reader |
| Queue-driven fan-out | Amazon SQS or Kafka feeding workers that write feeds |
Your practice
Design Inkwell Home for the workload above. Pull can't be fast enough and push can't keep up with the celebrities, so the design is the hybrid. Then make it cost as little as you can.