Skip to content

40. Social feed and stories

The archetype. An app in which people publish posts and short-lived stories, and each person reads an endless feed of what others published, chosen and ordered for that person.

A social feed app makes one promise. You open the app and fresh, relevant content is there at once, with no end.

Each clause of that promise costs something.

  • At once. The first screen cannot wait for a ranking request on a cellular network. Something must be on the device before the tap on the icon.
  • Fresh. What is on the device is old by definition. The app has to show it and replace it without disturbing the reader.
  • Relevant. Someone has to choose a few hundred items for this person out of millions. That work runs on the backend and takes time.
  • No end. The list has no last page. Paging must stay coherent through a feed whose order is computed per request.

A second promise sits beside the first. When you publish, your post looks published the moment you tap.

Four concerns dominate the design.

ConcernWhy it dominatesChapter
Lists, feeds and paginationThe product is one endless list with no stable sort keyChapter 15
ImagesMost of the bytes, most of the memory and most of the perceived speedChapter 16
PersonalizationThe order of the feed is the product, and a backend model decides itChapter 29
Caching and prefetching“At once” is met only by data that is already on the deviceChapter 11

Two more concerns shape parts of the design: ads (Chapter 27) and publishing (Chapter 18).

The archetype varies along two axes that change the design in a meaningful way.

AxisOne endOther end
OrderTime-ordered. Newest first, the same for any reader of the same sourcesRanked. A model scores items for this reader at this moment
Source of itemsFollowed accounts. The reader chose the sourcesInferred interests. The backend chooses from everything published

A feed of followed accounts can be time-ordered or ranked. A feed of inferred interests is always ranked, because nothing else could select it. Many products offer more than one feed, on separate tabs. Run the probes once per tab.

Stories are a second content form inside the same product. A story expires after a fixed time, plays full screen in a fixed sequence, and tracks which items each viewer has seen.

These are requirements as a user experiences them. None of them names a design.

What the app does

  • Show a home feed of posts, and supply more for as long as the user scrolls.
  • Bring newer posts on request, and say when newer posts exist.
  • Let the user like, share and comment on a post, and follow or unfollow its author.
  • Let the user publish a post with photos or video, and see it in their own feed.
  • Show a row of stories, mark which are unseen, and play them in order until each expires.
  • Notify the user of likes, comments, follows and mentions.
  • Show ads, labeled as such, among the posts and between stories.

Qualities it must have

  • The first screen has content within a moment of launch, on any network.
  • Scrolling is smooth through hundreds of items with media.
  • Nothing jumps. Images arrive into space that was reserved for them, and new posts do not push the reader’s place down the screen.
  • A tap on like responds before any round trip.
  • The feed does not repeat itself, and a refresh brings something new.
  • A story advances to the next one without a loading pause.
  • Publishing does not hold the user on a progress screen.
  • Data and storage use stay bounded.

Of the seven constraints of Chapter 2, three bind hardest. Human attention sets the bar for the first screen and for every tap. Limited resources bound what may be prefetched and kept. Unreliable network shapes the read path and the write path alike.

Run the general probes first. They place the app on the scales of Part II, and the archetype probes then separate the variants. Chapter 4 describes the probe families and how to run a probe cleanly.

Run these on the home feed. They are defined in Chapter 15.

ProbeTypical outcome for a social feedWhat it tells you
P15.1 Scroll rhythmAn indicator appears only during a fast flingThe next page is requested ahead of the end
P15.2 Page sizeA smaller first batch, then a constant larger oneA small first page for a fast first screen
P15.5 Refresh a ranked feed twiceMostly different items each timeA ranked feed that holds back what was shown
P15.6 New items while readingA new-items control, or nothing until a pull to refreshNew items are never inserted under the reader
P15.7 Leave and returnKept after the detail screen and a short absence, reset after a long oneThe loaded items are held, with a freshness time limit
P15.10 List against detailThe row shows the change at onceFeed and detail read one stored copy of the post

P15.5 settles the order axis. The same items in the same order on each refresh means a time-ordered feed, and you record that and skip the ranking probes.

P15.3 and P15.4 need a time-ordered list that a second device can change. Run them on the list of posts on your own profile, where the typical outcome is cursor paging or keyset paging.

Chapter 11 asks what the app keeps and what it fetches before you ask.

ProbeTypical outcome for a social feedWhat it tells you
P11.1 Memory or diskThe feed appears on both offline visitsThe loaded feed is on disk
P11.2 Read policyOld content at once, then newer content or a control that offers itStale-while-revalidate on the home feed
P11.6 Unvisited detailOnly what the feed row showed appears, and the comments fail to loadSeeding from the feed item. Comments are fetched on open
P11.7 Metered and unmeteredLess is ready ahead of time on cellularPrefetching is limited on a metered network

Run these in the feed on a weak connection. They are defined in Chapter 16.

Typical outcomes are these. P16.1 shows a tint per image or a blurred preview, so stand-in data travels in the feed item. P16.3 shows nothing moving when images land, so the item carries dimensions. P16.5 shows images on both offline visits, which means a memory cache and a disk cache. P16.7 fills the rows you stopped on first. P16.10 returns smaller dimensions and no location.

Chapter 29 supplies the probes for the ranking. Run them on each feed tab.

Typical outcomes are these. P29.4 shows a new account generic or popular content, or asks it for interests. P29.5 shows a clear shift on the refresh, and within the visit on a feed of inferred interests. P29.8 shows a new device as tailored as the old one, which means a backend profile.

Run P27.1 and P27.2 from Chapter 27. Typical is a labeled native ad every few posts and a full-screen ad between stories. In P27.2 the ad either stays in the cached feed, which means the backend inserted it, or its slot closes up, which means the client did.

Run P18.1, P18.2, P18.4 and P18.6 from Chapter 18 on a post with video. Typical is a short preparation phase, a transfer that continues by itself after an interruption and while the app is off screen, and a post that other accounts see only when it is complete.

These eight probes are specific to the archetype. Run them with a second account of your own, or with a person who agreed to help.

Open worksheet
Do
  1. Use the app normally, then leave it unopened for at least four hours on a device that stays online.
  2. Before you open it, recall or screenshot the items that were at the top of the feed on your last visit.
  3. Open the app on a good connection and watch the feed during the first two seconds.
  4. Repeat on another day, and this time turn on airplane mode before you open the app.
Watch

Whether the first thing drawn is the feed from your last visit, a skeleton, or items you have never seen, and whether anything appears in airplane mode.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Open the feed online and find a post from your own second account that you have not liked.
  2. Turn on airplane mode and tap the like control. Note the control and the count.
  3. Force-quit the app, open it again while still in airplane mode, and find the post.
  4. Turn the network back on and wait one minute without opening the post.
  5. On the second account's device, check who has liked the post.
Watch

Whether the like shows at once, whether it survives the force-quit, and whether it reaches the backend without another tap.

Caution. A like is visible to the author and to others. Run this on a post from your own second account.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Post something from account A and open the post on device 1, signed in as A. Note the like count.
  2. On device 2, signed in as your second account B, like the post.
  3. Watch device 1 for one minute without touching it.
  4. If the count did not change, pull to refresh on device 1. If it still did not change, refresh again after one minute.
  5. On device 1, like the post as A and note how fast the count moves.
Watch

When B's like reaches the count on device 1, and how that compares with the instant change from A's own tap.

Caution. This probe posts content. Limit the audience of the post to yourself or to your own second account.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Run this on a feed built from accounts you follow. Use account A, and an account C that A does not follow and that posted several times in the last days.
  2. Note the first three screens of A's feed.
  3. Follow C from A, refresh the feed and look for C's older posts.
  4. Have C publish a new post, refresh on A and look for it.
  5. Unfollow C from A, refresh once, and look for any of C's posts in the feed.
Watch

Whether C's older posts enter the feed when you follow, and whether C's posts leave it at once when you unfollow.

Caution. Following an account is visible to its owner. Use your own second account, or an account whose owner agreed.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Compose a post with a video of about a minute, or with several photos.
  2. Tap the share control and turn on airplane mode within two seconds.
  3. Note where the app takes you and what it shows for the post.
  4. Force-quit the app, open it again, then turn the network back on.
  5. Wait two minutes without tapping anything, then check the post from your second account.
Watch

Whether the post shows in your own feed as pending, whether it survives the force-quit, and whether it completes without a tap.

Caution. This probe posts content. Limit the audience of the post to yourself or to your own second account, and delete it afterwards.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. On an unmetered network, open the screen that shows the row of stories. Choose a time when at least five accounts have unseen stories.
  2. Wait fifteen seconds. Do not open any story.
  3. Turn on airplane mode.
  4. Open the first story and let it advance by itself, or tap through, across as many accounts as will play.
Watch

How many items play, across how many accounts, before a loading indicator appears.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Sign in to account A on two devices, both online, and open the row of stories on both.
  2. Have your second account B post a story with two items. Confirm that both devices show B as unseen.
  3. On device 1, watch both items of B's story to the end.
  4. Watch device 2 for thirty seconds without touching it.
  5. If B still shows as unseen on device 2, pull to refresh, or leave the screen and return.
Watch

Whether device 2 marks B's story as seen by itself, after a refresh, or not at all.

Caution. Viewing a story can show the author that you viewed it. Use a story posted by your own second account.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Do
  1. Post a story from account B and note the exact time.
  2. On device 1, signed in as A, watch the story once, then turn on airplane mode and confirm that it still plays.
  3. Keep device 1 in airplane mode until a few minutes past the moment the story is due to expire. Do not open the app in between.
  4. Open the app on device 1, still in airplane mode, and look for the story.
  5. Turn the network back on and look again.
Watch

Whether the expired story still plays on the offline device, and what changes when the device comes online.

Caution. This probe posts content. Limit the audience of the story to your own second account.

Read

Select what you saw to record it in your worksheet. Select it again to clear it.

Each subsection states the design this archetype usually takes and the reason. The components are those of the universal app model in Chapter 3.

Chapter 15 separates a time-ordered list from a ranked feed. A time-ordered feed of followed accounts has a sort key, the time of publication, and pages by cursor like any list. A ranked feed has no key. Its order exists only in the answer to one request.

The usual answer is a session snapshot. On the first request of a visit, the backend gathers candidate items, scores them for this user, stores the ordered result under a session, and returns the first page with a cursor into that stored order. Later pages read the same stored order. Paging is stable, cheap and free of repeats.

A pull to refresh ends the session and starts a new one. The backend ranks again and holds back what the user was already shown. This is why P15.5 typically returns mostly different items, and it implies that the backend keeps a record of what each user has seen.

The second design from that chapter, ranking each page with shown items excluded, lets the feed react while the user scrolls. Feeds of inferred interests lean toward it. From outside the two differ only in feedback delay. If P29.5 shows the feed shifting before any refresh, the order was not frozen at the start of the visit. A claim beyond that is Speculative.

The client’s part is small and strict.

  • It requests the next page ahead of the end, so that scrolling never meets a loading row at reading speed.
  • It never inserts items above the reader. Newer items wait behind a new-items control, or behind the pull gesture.
  • It removes any item it already holds from an arriving page, because a ranked backend can return a repeat when its record of shown items lags.
  • It reports what was actually on screen, and for how long, as interaction events. The backend’s record of shown items and the model’s inputs both depend on it.

The promise says “at once”, and a ranking request cannot meet it. The design that does is the stale-while-revalidate read policy of Chapter 11, applied to the first page of the feed.

The client writes the loaded feed to disk as it arrives: the items of the first pages, in order, with the session cursor. At launch it draws from that copy before the network client has opened a connection. The refresh runs behind it. P40.1 shows which of three variants the app takes.

  • Cached, then refreshed. The old feed shows, and the fresh one replaces it or waits behind a control. This is the common design.
  • Loading, then fresh. A skeleton covers the wait.
  • Fetched ahead of opening. A background task or a push hint refreshed the stored feed before the launch. The first screen is both instant and fresh. The cost is data and battery spent on launches that may not happen.

The replacement step needs care. If the fresh feed simply overwrites the cached one a second after launch, the post the user had begun to read vanishes from under their finger. Well-built clients replace the feed only while the user has not scrolled or touched it, and otherwise keep the cached feed and offer the fresh one through the new-items control.

The stored feed also carries a time limit. Returning after a few minutes restores the position. Returning after some hours resets to the top with a new session. P15.7 estimates that limit.

Three things are prefetched in a feed, in rising order of cost: the next page of items, the images for rows just outside the visible range, and the start of a video that is about to scroll into view. The last is limited or disabled on a metered network.

The post detail screen is not prefetched. It is seeded: the feed item already holds the author, media reference, caption and counters, so the detail screen draws them at once and requests only the comments. P11.6 shows this as a detail screen that is complete except for its comment list.

The feed takes the designs of Chapter 16 nearly without exception, because every one of them removes a visible defect.

  • Image variants. The backend serves a size that fits the device’s screen. A feed row never downloads a full camera image.
  • Dimensions and a stand-in in the item data. The layout is final before any image request is made.
  • A memory cache and a disk cache. Scrolling back is instant, and the first screen at launch has images as well as text.
  • Cancellation and priority. A fling through fifty rows must not queue fifty downloads ahead of the rows where the user stops.
  • Preparation before upload. A photo is resized and its metadata is removed, which shortens the upload and protects the author.

Video in the feed follows Chapter 17: one item plays at a time, muted, and a poster image stands in until it starts. The archetype chapter Video and short-video streaming covers the case where video is the whole product.

The feed is ranked by a backend model, because a recommendation model needs the behavior of every user (Chapter 29). The pipeline has three steps, and none of them is visible from outside.

  1. Gather candidates. A few hundred to a few thousand items: recent posts from followed accounts, and items that resemble what the user engaged with.
  2. Score. The model estimates, for each candidate, how likely this user is to engage with it.
  3. Assemble. Rules adjust the scored list. They limit runs of posts from one author, mix content forms, and place ads.

What is visible is the loop around it. The client records interaction events: what was shown, how long it stayed on screen, what was tapped, liked, shared or hidden. It uploads them in batches while the app is in use. The backend turns them into model inputs for this user. P29.5 measures the length of that loop, and for this archetype it is typically one refresh.

A new account has no inputs. It is shown a default ranking, or asked for interests, or offered accounts to follow. P29.4 shows which.

A like is the smallest mutation in the app and the most frequent. It takes the optimistic update of Chapter 12: the UI changes first and the request follows.

function toggleLike(post):
transaction(localStore):
post.likedByMe = not post.likedByMe
if post.likedByMe: post.likeCount += 1
else: post.likeCount -= 1 // a display estimate, not the truth
outbox.replace(key: likeKey(post.id), desired: post.likedByMe) // latest intent wins
syncEngine.requestRun()
function onPostFetched(fresh):
transaction(localStore):
pending = outbox.find(likeKey(fresh.id))
if pending:
// keep the user's intent, and adjust the fetched count only if it lacks that intent
post.likedByMe = pending.desired
post.likeCount = fresh.likeCount + differenceOf(pending.desired, fresh.likedByMe)
else:
post.likedByMe = fresh.likedByMe
post.likeCount = fresh.likeCount

Three choices in this code are design decisions that probes expose.

The mutation states a desired end state, liked or not liked. It is not a toggle. A request that says “liked is true” can be retried any number of times with the same result, so no idempotency key is needed. Tapping like, unlike and like again offline leaves one entry in the outbox.

The count on screen has two sources. The client’s own tap moves it by one. Every fetch replaces it with the backend’s number, which includes the likes of everyone else. P40.3 shows both: an instant step from your tap, and a jump on refresh. The step is an estimate and the fetched value is the reconcile.

The backend’s number is itself approximate. A post that receives thousands of likes a minute cannot be counted by a query on each read. The backend keeps a separate counter, updates it asynchronously from the stream of likes, and serves it from a cache. The signs are a rounded figure, a count that lags the list of people who liked, and a count that is briefly old after a refresh.

A rejected like is rare: the post was deleted, or the author blocked the viewer. The client rolls the change back silently. A like has too little value to deserve a failed state.

Publishing is the upload, then attach design of Chapter 18.

  1. The client prepares the media: it resizes photos, and transcodes video to a size worth uploading.
  2. The transfer worker uploads each file to media delivery as a resumable transfer and receives a reference.
  3. The sync engine sends the mutation that creates the post, with the references, the caption and a client-generated id as the idempotency key.
  4. The backend makes the post visible. For video it may first wait for processing into quality levels.

From the tap on share, the client shows the post in the author’s own feed as an optimistic item with a progress indicator. P40.5 tests whether that item and its transfer are durable.

The order matters to everyone else. With upload, then attach, no follower ever sees a post with a hole where the media should be. P18.6 confirms it from a second account.

Many clients begin the upload while the author is still writing the caption. The share tap then completes faster than the file size can explain.

A story is a short sequence of full-screen items from one account. Three properties set it apart from a post, and each has a design consequence.

Stories expire. Each item carries an expiry time, set by the backend at publication. The backend stops returning the item after that time. A careful client also checks the time itself before it draws the row, so that a device which was offline at the limit does not show a dead story. P40.8 separates the two.

Stories play in a fixed sequence. The row of stories is a small list: one entry per account, ordered by the backend, usually unseen first. Tapping an entry starts a player that advances through that account’s items and then into the next account. The sequence is known in advance, which makes it the ideal case for prefetching. The client fetches the first unseen item of the next few accounts as soon as the row loads, and while one item plays it fetches the one after. P40.6 measures how far ahead that goes. The reward is a viewer that never shows a loading indicator. The cost is media fetched for stories the user never opens, so the depth is reduced on a metered network.

Stories track who has seen what. Seen-state has two readers. The viewer needs it to order the row and to resume at the first unseen item. The author needs it for the list of viewers. The viewer’s side is the story counterpart of a read position: one value per viewer per author, holding the newest item that viewer has seen. It is tiny, it is written often, and it must survive going offline in the middle of a story. The client writes it to the local store at once and queues the report in the outbox. P40.7 shows whether the backend stores it and how it reaches the account’s other devices.

Assembling the feed: write time or read time

Section titled “Assembling the feed: write time or read time”

The backend can build each user’s feed at two moments. Chapter 14 introduces fan-out, and the same choice appears here with posts in place of messages.

Fan-out on writeFan-out on read
What happens at publicationThe post’s id is copied into a stored feed for each followerThe post is appended to the author’s own list
What happens when a reader opens the feedOne read of that reader’s stored feedThe lists of every followed account are read and merged
Cost of a readLow and constantGrows with the number of accounts followed
Cost of a writeGrows with the number of followersLow and constant
Follow and unfollowAffect only future posts, unless the stored feed is repairedTake effect on the next read

Readers far outnumber writers, so a small product leans to fan-out on write. The weak point then forces a hybrid at size: ordinary accounts fan out on write, and the posts of very popular accounts are merged in at read time.

A ranked feed changes the meaning of the stored feed. It no longer is the feed. It is one source of candidate items, and the ranking step reads it together with other sources.

This is the least observable part of the design. P40.4 gives the only view of it, and only on a time-ordered feed: posts that linger after an unfollow point to a stored feed. Record any other result as Speculative.

The feed’s ads are native ads, the format Chapter 27 describes as an ad drawn as an item inside a feed. That chapter gives two ways for the ad to enter, and this archetype usually takes the first.

  • The backend inserts it. The assembly step places ads among the ranked items and returns one list. The ad is cached, paged and prefetched like a post. It appears in the cached first screen offline.
  • The client inserts it. An ad component fills fixed positions from its own requests. The ad arrives late and is missing offline.

A product that sells its own ads inserts them on the backend: one request, no layout shift, and frequency limits enforced where the ranking happens. The client’s duty is measurement. It reports when an ad was on screen and for how long, through the same interaction events as posts.

A comment list is a time-ordered or ranked list of its own, loaded when the post opens. It pages by cursor. A new comment is an optimistic item with a client-generated id, sent through the outbox, and marked failed if rejected, since a comment has more value than a like.

Notifications are built on the backend from activity on the user’s posts and sent by platform push (Chapter 23). Likes are grouped: a replacement key lets one notification replace another, so a popular post does not produce a hundred alerts. A tap on a comment notification is a deep link that enters the comment list at an anchor, the case P15.9 tests.

Figure 40.1 shows the components that the probes imply for the most common variant: a ranked feed of followed accounts with a stored feed per user as a candidate source.

Figure 40.1. The client and backend of a ranked social feed. Solid lines are the read path and the write path. Dotted lines are asynchronous.

In the terms of the universal app model, the feed service and the ranking model are services, the post store and the stored feeds are the backend store, and fan-out is async delivery.

The client has a sync engine, and it is a small one. It drains an outbox of likes, follows, comments, seen-state and new posts. It does not pull a replica. The read side is a cache, which places the feed at Level 1 for reads and Level 2 for writes on the offline scale of Chapter 12.

Figure 40.2 follows the archetype’s most important flow: opening the app, from the cached first screen to the first optimistic like.

Figure 40.2. Opening a ranked feed. The cached first screen is drawn before the ranking request returns.

The yellow phase is the cost of relevance, and the cached first screen exists to hide it.

Each axis from Section 40.1 is settled by one or two probes. Chapter 5 describes how a signal becomes a claim with a confidence word.

AxisDiscriminating probeReading
OrderP15.5The same order on every refresh means time-ordered. Reordered or new items mean ranked
Source of itemsPassive observation, then P29.4Authors you never followed in the feed mean inferred interests. A new account that sees a full feed without following anyone confirms it
Feed assemblyP40.4Lingering posts after an unfollow mean a stored feed per user

Time-ordered against ranked. A time-ordered feed is the simpler system. It needs no model, no record of shown items and no session snapshot. It can store pages on the device and fill a gap after an absence, as P15.8 tests. Its weakness is a product one: a reader who follows many accounts misses most of what they post. A ranked feed is always full, and it cannot be resumed or compared between two people.

Followed accounts against inferred interests. A feed of followed accounts has a bounded candidate set and a natural default, an empty feed with suggestions. A feed of inferred interests draws from everything. It must work for a new account from the first second, so its default ranking matters more, and it learns from time on screen, because the user has stated nothing. Its feedback delay is the shortest in the product.

The signal table collects the behaviors from all eight probes and from normal use.

SignalDesign it points toConfidence
The feed from the last visit shows the moment the app opens, and newer items arrive a second laterCached first screen read from disk with a refresh behind itInferred
A pull to refresh brings mostly different items each timeRanked feed that builds a new session snapshot per refresh and holds back what was shownInferred
A deep scroll in a ranked feed never repeats an itemPaging through a session snapshot by cursor, or ranking each page with shown items excluded. The two look the sameSpeculative
A like fills in at once in airplane mode and is still there after a force-quitOptimistic update with a durable outboxInferred
A like count moves by one on your tap and jumps to a different number on the next refreshClient estimate replaced by the fetched counterInferred
Large counts show as rounded figuresCounters aggregated asynchronously and served as approximate valuesInferred
The list of people who liked a post is ahead of or behind the numberThe counter and the list are stored and updated separatelyInferred
Each image area shows its own tint or a blurred preview before the image arrivesStand-in data computed at upload and carried in the feed itemInferred
A post you publish appears in your own feed at once with a progress barOptimistic item with upload, then attachInferred
Other accounts see a new post only when its media is completeUpload, then attach. The mutation waits for the media referenceInferred
Stories from several accounts play in airplane mode without having been openedStory media prefetched in viewing orderInferred
A story watched on one device shows as seen on anotherSeen-state stored on the backend per viewer and authorInferred
An expired story disappears on a device that has been offline since before the limitExpiry time carried in the item data and enforced by the clientInferred
Posts from an account stay in the feed for a while after you unfollow itStored feed per user filled at write timeInferred
Posts from very popular accounts arrive with the same delay as any otherHybrid assembly, with popular accounts merged at read time. One of several explanationsSpeculative
A sponsored post appears in the cached feed in airplane modeNative ad inserted by the backend and returned as a feed itemInferred
A sponsored post fills in a moment after its neighbors and is missing offlineNative ad inserted by the client from its own ad requestInferred
A comment notification opens the post with the comment in viewDeep link that enters the comment list at an anchorInferred

Search the signals from every chapter in the signal lookup.

The client of a small feed app and of a very large one are nearly the same. The backend is where size shows, and most of that is hidden.

The same at every size

  • A cached first screen with a refresh behind it, and paging by cursor.
  • Item data that carries image dimensions and a stand-in.
  • Optimistic likes that state a desired end state.
  • Upload, then attach for posts.

What changes

  • Ranking. A small app sorts by time, or by a simple score computed in the query. A large one runs candidate gathering, a model and an assembly step, with a store of per-user model inputs fed by event streams.
  • Feed assembly. A small app reads the posts of followed accounts at request time, in one query. A larger one stores a feed per user. A very large one adopts the hybrid of Section 40.4 for its most followed accounts.
  • Counters. A small app counts rows. A large one keeps approximate counters updated from a stream, and shows rounded figures.
  • Ads. A small app has none, or takes them from an ad source through a client component. A large one sells its own and inserts them at assembly.
  • Experiments. At size, two accounts differ for reasons that are not personal, because ranking and layout are under experiment (Chapter 30). Repeat any surprising probe on a second account.

From outside you see rounded counts, a delay before a post reaches followers, and feeds that differ between similar accounts. Each is a Speculative signal of scale, because a product decision explains it equally well.

Chapter 6 describes the worksheet and the order in which to fill it in. The fields in this form are specific to the archetype. The probes of Section 40.3 fill most of them.

Open worksheet

The fields that Part II adds to the worksheet take these typical values for the home feed of a ranked social app. A value that differs in your teardown is a finding. Record it with the probe that produced it.

FieldTypical value for a social feedConfidence
From Chapter 15
List orderRankedObserved
Paging methodCursor or keyset, into a session snapshotSpeculative
Ranking stability on refreshNew items each timeObserved
How new items arriveNew-items control, or gesture onlyObserved
From Chapter 11
Read policyStale-while-revalidateInferred
Detail screen data sourceSeeded from listInferred
Prefetch on metered networkReduced on meteredInferred
From Chapter 16
Stand-in before arrivalDominant color or blurred previewObserved
From Chapter 29
Where the model runsBackendInferred
Feedback delayOn refreshObserved
From Chapter 27
Ads presentNative ads in the feed, full-screen ads between storiesObserved
From Chapter 18
Order of file and itemUpload then attachInferred
From Chapter 12
Offline level1 Cached reads for the feed, 2 Queued writes for likes and postsInferred

The question is usually asked as “design a news feed” or “design the home screen of a photo-sharing app”. The read path, the write path and the ranking each open into a deep topic, and the candidate has to choose.

Clarify first. Ask four things.

  1. Time-ordered or ranked, and built from followed accounts or from inferred interests.
  2. Which media: text, photos, video.
  3. Whether the question is about the client, the backend, or both.
  4. Whether stories, ads and notifications are in scope.

Then state the promise from Section 40.1.

Go deep on three concerns.

  • The read path. A cached first screen, stale-while-revalidate, cursor paging into a session snapshot, a new-items control. Say why offset paging fails on a feed, and why a ranked feed has no page two without a snapshot. Draw Figure 40.2.
  • Feed assembly. Fan-out on write against fan-out on read, with the cost of each, the account with millions of followers, and the hybrid. This is the part most interviewers are waiting for when the question is about the backend.
  • Media. Variants sized for the device, stand-in data and dimensions in the item, two cache tiers, cancellation on fast scroll. For the write side, upload, then attach with a resumable transfer.

Trade-offs an interviewer expects to hear.

  • Showing a stale first screen against showing a skeleton.
  • A frozen order per session against a feed that adapts as the user scrolls.
  • An exact counter against an approximate one that scales.
  • For a like, a silent rollback against a visible failure, and why a comment or a post deserves the second.

Leave stories until the feed is drawn. Then present them as the same parts with three changes: an expiry time, a fixed sequence that invites prefetching, and seen-state as a position per viewer and author.

  • A social feed app promises fresh, relevant content at once, with no end. Lists and feeds, images, personalization and caching dominate its design.
  • “At once” is met by a cached first screen read from disk, with a refresh behind it. “No end” is met by cursor paging, with the next page requested ahead of the end.
  • A ranked feed has no stable order, so the backend stores one per session as a session snapshot, or ranks each page with shown items excluded. A refresh starts a new ranking and holds back what was seen.
  • Likes are optimistic updates that state a desired end state. The count on screen is the client’s estimate until the next fetch, and the backend’s count is itself approximate at size.
  • Publishing is upload, then attach. The author sees an optimistic item, and followers see the post only when it is complete.
  • Stories are items with an expiry time, played in a known sequence that the client prefetches, with seen-state kept as one position per viewer and author.
  • The backend builds each feed at write time, at read time, or with a hybrid for very popular accounts. Only an unfollow on a time-ordered feed gives a view of this from outside.
  • Native ads enter the feed at assembly on the backend, or from a client component. P27.2 separates the two.