TikTok’s home screen is not a list of posts from people you follow. It is the For You feed: an endless column of short videos, each chosen for you by a recommender, each playing the instant it appears, each swipe teaching the system what you want next. TikTok said in 2021 that more than 1 billion people use it every month. That makes this a different problem from Instagram or Twitter. The hard parts are a serving pipeline that picks 8 videos out of a billion in about 150 ms, a learning loop that turns a skip into a better next batch within minutes, fair first exposure for videos nobody has seen yet, and playback that never waits on the network.
Understand the problem
Say the shape out loud first: the feed is a recommendation problem, and the follow graph is just one signal. A creator with 12 followers can reach millions of people if their video holds attention. That moves the centre of gravity away from fan-out, which dominates a follow-based feed, towards retrieval, ranking and feedback loops.
Functional requirements
- Users should be able to upload a short video (up to 60 seconds here) with a caption and a sound, and have it shown to an audience that wants it.
- Users should be able to scroll an endless, personalised For You feed in which every video starts playing immediately.
- Users should be able to like, comment, share and follow, and the feed should react to what they do — including what they skip — within the same session.
- Users should be able to trust that For You never repeats a video (we remember 90 days of viewing), and should see view and like counts.
Non-functional requirements
- Scale. Over 1 billion monthly users; assume 500 million daily active users watching about 150 videos a day each.
- Instant playback. A swipe shows the next video’s first frame in under 100 ms at p50 and 300 ms at p99. Opening the app plays the first video within a second on 4G.
- Fast recommendations. The feed API returns a batch in under 150 ms at p99, so the client can request the next batch long before it runs out.
- Freshness of learning. An action should shape the next batch within seconds; model parameters should reflect new behaviour within minutes, not the next day.
- Fair, safe distribution. A new video that passes review reaches its first test audience within about 10 minutes; nothing reaches For You before automated review has run.
- Availability over exactness. 99.95% for the feed. If the recommender is degraded, serve a cached or trending feed rather than an error; counts may lag by seconds.
In the interview
Ask: “Is the main feed recommended or follow-based?” “How long are videos?” “What does success mean — watch time, retention, satisfaction?” and “How fast must the system react to a new interest?” The last two answers decide your ranking objective and whether you need online training. Then say it: “I’ll optimise for long-term watch time and satisfaction, not clicks, and I want the model to learn within minutes.”
Out of scope
- Long-form video and the full transcoding pipeline. Covered in design YouTube; we only note what short video changes.
- Live streaming, duets, effects and editing. Separate products.
- Direct messages. See design WhatsApp.
- Ads. They are mixed into the same feed with a separate auction; we leave slots for them.
- The internals of TikTok’s models. TikTok has published how For You behaves, not how it is built. Everything below is a design you could defend, using public papers for the parts that are hard.
The set-up
Plan the approach
There are three loops. The serving loop answers “which 8 videos next?” within 150 ms. The learning loop turns what people watched into better models within minutes. The media loop gets uploads transcoded, reviewed and onto CDN edges before the recommender sends viewers to them. Build each requirement simply, then spend the deep dives on the funnel, the learning loop, cold start, instant playback, the seen filter and counting.
Core entities
- Video: ID, creator, caption, sound ID, duration, status (
processing,in_review,eligible,limited,removed), renditions, and a content embedding computed from frames, audio and text. - User: ID, language, region, interests picked at sign-up, and a user embedding that changes every session.
- Impression: one video shown to one user — request ID, position in the batch, and a snapshot of the features the ranker used.
- Engagement event: watch time, completed, rewatched, skipped after N ms, liked, shared, commented, followed, “not interested”.
- Seen set: the videos a user has already been shown.
- Exposure stage: for each new video, which test pool it is in and how it is doing.
API
Uploads work like any large media upload: the app asks for a signed URL, sends the file in resumable chunks straight to object storage, then publishes.
POST /v1/videos
Idempotency-Key: 5d1e-…
{ "upload_id": "up_93Fz", "caption": "first try at this recipe", "sound_id": 7012606465, "visibility": "public" }
{ "video_id": 7302218840155, "status": "processing" }
The feed returns a small batch plus everything the player needs to start instantly:
GET /v1/feed/foryou?session=s_41aa&count=8
{
"request_id": "r_8c02",
"videos": [{
"id": 7302218840155,
"creator": { "id": 551, "handle": "@chef.meera" },
"duration_ms": 27000,
"cover": "https://v.example-cdn.net/7302…/cover.webp",
"first_segment": "https://v.example-cdn.net/7302…/540p/seg0.m4s",
"manifest": "https://v.example-cdn.net/7302…/master.m3u8",
"stats": { "likes": 18400, "comments": 312 }
}],
"prefetch": 3
}
Events are batched by the app every few seconds or every few videos:
POST /v1/events
{ "request_id": "r_8c02", "events": [
{ "video_id": 7302218840155, "watch_ms": 27000, "loops": 2, "liked": true },
{ "video_id": 7302218840190, "watch_ms": 1100, "skipped": true }
] }
Likes, comments, follows and shares are ordinary idempotent writes (POST /v1/videos/{id}/likes and so on); they also appear in the event stream.
Back-of-envelope estimates
(See back-of-envelope estimation for the method.)
Views. 500M users × 150 videos = 75B views a day. 75B ÷ 86,400 ≈ 870k views a second, about 1.7M at the evening peak.
Feed requests. 8 videos per request: 75B ÷ 8 ≈ 9.4B a day ÷ 86,400 ≈ 110k requests a second, about 220k at peak.
Ranking work. If the heavy ranker scores 500 candidates per request: 110k × 500 = 55M model predictions a second, 110M at peak. That number is why there is a cheap pre-ranking stage, and why retrieval cannot simply hand everything to the ranker.
Events. Assume 3 events per view (impression, watch progress, an interaction): 75B × 3 = 225B a day ÷ 86,400 ≈ 2.6M events a second. At about 200 bytes each, 45 TB of logs a day.
Uploads and storage. Assume 50M uploads a day: 50M ÷ 86,400 ≈ 580 a second, 1,700 at peak. A 30-second original of about 10 MB plus renditions of about 15 MB is 25 MB per video: 50M × 25 MB = 1.25 PB a day, about 456 PB a year.
Egress. An average view lasts about 20 seconds at about 1.2 Mbps: 20 × 1.2 = 24 Mb = 3 MB. 870k views/s × 3 MB ≈ 2.6 TB/s × 8 ≈ 21 Tbps on average. Every 10% saved on bitrate or wasted prefetch is 2 Tbps.
Seen sets. 150 videos a day for 90 days = 13,500 video IDs per user. As 8-byte IDs that is 108 KB per user, or 54 TB for 500M users. As a Bloom filter at a 1% false-positive rate (9.6 bits per item) it is about 16 KB per user, about 8 TB in total.
Embeddings. Say 1B users and 2B active videos, 64 floats each: 3B × 256 bytes ≈ 770 GB just for ID embeddings; with all the other sparse features, recommendation models run to terabytes. An approximate nearest-neighbour index over 1B video vectors needs about 512 GB as an HNSW graph, or about 40 GB with 32-byte product-quantised codes.
Three numbers shape the design: 55M predictions a second (so the funnel), 2.6M events a second (so a streaming pipeline, not nightly batches), and 21 Tbps of video (so every byte you prefetch must be a byte the user will probably watch).
High-level design
Users should be able to upload a short video
The upload path is the familiar one — signed URL, resumable chunks straight to object storage, then a publish call (see large file uploads). What changes for a recommendation-first app is what must happen before anyone is sent to the video.
- Transcode into a short ladder — say 360p, 540p, 720p and 1080p in H.264, plus a more efficient codec for phones that can decode it — cut into 2-second parts so a player can start after the first part. A 30-second clip is small enough to transcode on one machine in seconds, without splitting it across a fleet the way long videos are.
- Understand the video: a content embedding from frames, audio and caption text, a fingerprint of the sound so it can be linked to the sound’s page, and language detection. This embedding is what lets the recommender place a video nobody has watched yet.
- Review automatically. TikTok’s Safety Center says content is reviewed by automated moderation before it is posted, checking both its rules and separate For You eligibility standards, and TikTok’s own explainer says videos that are newly uploaded or under review may be ineligible for recommendation. A video can be allowed on the creator’s profile but never recommended.
When all three finish, the video enters the recommender’s fresh pool. Ten minutes from upload to first viewers is a realistic budget.
Users should be able to scroll a personalised For You feed that starts instantly
The simplest recommender that works: when the phone asks for more videos, the feed service gathers candidates from a few sources, scores them with one model, and returns the top 8 with the URLs of their first segments.
The feed service keeps a little session state — what it served in the last few batches — so two batches never overlap; the phone asks for the next batch when it has about three videos left. The phone plays from the CDN and prefetches the first part of the next few videos. Already, two rules from TikTok’s own explanation apply: don’t show two videos in a row from the same creator or with the same sound, and don’t show anything the user has seen before. Both get deep dives.
Users should be able to like, comment, share and follow — and the feed should react
Explicit actions are the minority. Most of what the recommender learns comes from implicit signals: how long you watched, whether you watched to the end or looped it, how fast you swiped away. TikTok’s explainer says a strong signal such as finishing a longer video weighs far more than a weak one such as being in the same country as the creator.
The app batches events and sends them every few seconds; a collector validates and de-duplicates them into a log such as Kafka. Three consumers read the same log: counters for the numbers on screen, live features such as “this user’s last 50 videos and how long they watched each” and “this video’s completion rate in the last 10 minutes”, and training, which turns events into labelled examples. The ranker reads live features on every request, so a skip changes the very next batch even before any model weight has changed.
You now have a recommender that works for a few million users. The deep dives are about doing the same thing for a billion people in 150 ms, and learning fast enough to matter.
Deep dives
How does the For You pipeline pick 8 videos out of a billion in 150 ms?
No model can score a billion videos 110,000 times a second. The answer — the shape YouTube described in 2016 and most large recommenders share — is a funnel: cheap methods narrow millions of videos to thousands, a mid-sized model narrows thousands to hundreds, and only then does the expensive model run. Each stage spends more per candidate on fewer candidates.
Retrieve from several sources, in parallel, each with a quota and a deadline. One source is never enough: an embedding model finds “more of what you like”, but not the creator you just followed or the song that started trending an hour ago. A reasonable mix per request:
- Two-tower nearest neighbours (about 1,000): videos whose embedding is close to yours. The workhorse; explained below.
- Item-to-item (about 300): videos similar to the last few you finished or liked — the fastest way to follow a new interest within a session.
- Following (about 100): recent videos from creators you follow, so the follow button means something.
- Trending (about 200): rising videos in your region and language.
- Fresh pool (about 200): new videos under test that match your interests (see cold start).
- Exploration (about 50): interests adjacent to, but outside, your profile. TikTok says it deliberately mixes in videos that don’t seem to match your interests, partly to discover what you like and to avoid a filter bubble.
Every source has a deadline of about 30 ms. A slow source is dropped for that request rather than holding up the batch.
Filter. Remove videos you have already seen (a later deep dive), creators and sounds you blocked or marked “not interested”, anything not eligible for your age or region, and near-duplicates such as the same clip re-uploaded by ten accounts.
Pre-rank. A small model — often just the two towers’ dot product plus a handful of cheap features — trims about 1,500 candidates to 500. It costs microseconds per candidate.
Rank. The heavy model sees hundreds of features per (user, video) pair and predicts several outcomes at once: probability of finishing, expected watch time, rewatch, like, share, comment, follow, a skip in the first two seconds, and “not interested”. The final score is a weighted sum, for example 0.5 × P(finish) + 0.2 × P(like) + 0.4 × P(share) + 0.3 × P(follow) − 1.0 × P(not interested). Choosing those weights is a product decision. YouTube’s paper explains the key one: ranking by clicks promotes “clickbait” that people don’t finish, while watch time captures engagement. For short video, divide watch time by duration or a 60-second clip will always beat a 9-second one.
Re-rank. Apply rules that a per-item score can’t see. TikTok says its feed generally won’t show two videos in a row with the same sound or by the same creator; reserve one slot in eight for exploration and one for a fresh-pool video; leave slots for ads; and boost videos whose renditions are already warm on your nearest CDN edge.
Two-tower retrieval. The source that does most of the work deserves a closer look.
- Training. Each training example is a user and a video they watched to the end. The other videos in the same training batch act as negatives — cheap, but biased: popular videos appear in many batches, so the model learns to push them down too hard. Google’s two-tower paper corrects this by estimating each item’s frequency from the stream and adjusting the scores, and deployed it to retrieve from tens of millions of YouTube videos.
- Cold videos still get vectors. Because the video tower reads content features (frames, audio, caption, creator), a video uploaded five minutes ago gets a sensible vector before anyone has watched it. That is the main reason to prefer two towers over pure ID-based matrix factorisation here.
- The index. With 1B video vectors of 64 floats, an HNSW graph is fast and accurate but needs about (64 × 4 + 32 × 2 × 4) = 512 bytes per vector at 32 links, about 512 GB, and does not support removing vectors. An IVF index with 32-byte product-quantised codes needs about 40 GB including IDs, at some cost in recall, which you buy back by re-scoring the shortlist with full vectors. The Faiss guidelines walk through exactly this trade. Shard the index by video ID, query every shard in parallel, and merge their top results; send a hedged duplicate request if one shard is slow.
- Freshness of the index. Rebuild the big index daily; keep a small, flat index of the last day’s videos that is updated every few minutes and queried alongside it. Removed or limited videos are filtered with a blocklist at query time, which is easier than deleting from a graph.
In the interview
The sentence that shows you know recommenders: “Retrieval optimises recall — don’t miss anything good; ranking optimises precision — put the best first. They are different models with different budgets, and I measure them separately.”
How does the model learn from a swipe within minutes?
Interests move fast. ByteDance’s Monolith paper puts it bluntly: the same user can shift interest “every next minute”, so the data is never stationary. A song starts trending at lunch; by evening, a model trained last night has never seen the videos using it, and its ID embeddings for those videos are empty. Three speeds of learning fix different parts of this:
- Seconds — live features. The ranker reads “your last 50 videos and how long you watched each” straight from the stream. No weights change, but the next batch already reacts.
- Minutes — online training of embeddings. User and video embeddings update continuously from new examples and are pushed to serving.
- Days — dense weights and full retrains. The deep network’s shared layers change slowly and can be refreshed daily.
Bad: retrain nightly in batch. Simple, reproducible and easy to validate before release. But the model is up to 24 hours stale, new videos have no learned embeddings until tomorrow, and a trend can rise and fall before the model notices.
Good: hourly incremental training. Fine-tune yesterday’s model on the last hour of data and redeploy. Much fresher, but you still ship whole models — terabytes — which means slow transfers and doubled memory on the serving machines during the swap.
Great: streaming online training with incremental parameter sync. This is the design ByteDance describes in Monolith:
- Log features at serve time. When the model server ranks a request, it logs the exact features it used, keyed by request ID. Recomputing them later at training time invites training–serving skew, where the model learns from values it never actually saw.
- Join actions to impressions. A stream job — Monolith uses Kafka with a Flink joiner — pairs each action with its served features by request ID. Actions arrive late and out of order (a loop is only known when the video ends), so recent features wait in memory and older ones in an on-disk key-value store.
- Sample negatives, then correct. Skips vastly outnumber likes, so the joiner keeps only a fraction of negatives; Monolith applies a log-odds correction at serving so predictions stay calibrated.
- Train online. Trainers consume examples continuously and update a training parameter server.
- Sync only what changed. Every minute, push the embeddings touched since the last sync to the serving parameter servers; push the slow-moving dense weights daily at the quietest hour. The paper’s example: 100,000 IDs updated in a minute × 1,024 floats × 4 bytes ≈ 400 MB a minute, instead of a multi-terabyte model.
Does freshness pay? On a public ad-click dataset, Monolith reports online AUC of 79.66 with a 5-hour sync interval, 79.78 at 1 hour and 79.80 at 30 minutes — small-looking numbers that matter a lot at this scale — and a week-long live A/B test on a production ads model in which online training beat batch training every day.
| Nightly batch | Hourly incremental | Streaming online (Monolith-style) | |
|---|---|---|---|
| Staleness | Up to 24 h | About 1 h | About 1 min for embeddings |
| New video gets a learned embedding | Next day | Next hour | Minutes after first views |
| What ships to serving | Whole model | Whole model | Touched embeddings; dense weights daily |
| Validation before serving | Easy: offline evaluation | Possible | Hard: needs online guardrails |
| Main risk | Misses trends | Big, slow swaps | Bad data reaches users fast |
Embedding tables that grow without collisions. The usual trick for unbounded IDs is to hash them into a fixed-size table. That is cheap, but two IDs that hash to the same row share one vector, and with billions of video and user IDs collisions are common — a viral video and a brand-new one can end up trained as the same thing.
Monolith instead stores sparse parameters in a cuckoo hash map — two tables and two hash functions; an insert that lands on an occupied slot evicts the occupant to its other slot — which gives every ID its own row with constant-time lookups. Two observations keep it from eating all memory: IDs seen only a handful of times are under-trained and barely affect predictions, so they are admitted only after a tunable number of occurrences (with a probabilistic filter in front); and IDs that go quiet — a user who left, an old video — expire after a tunable idle period.
Fault tolerance can be relaxed. Online training sounds fragile, but Monolith found daily snapshots of the training parameter servers enough. With 1,000 parameter servers and a 0.01% daily failure rate, one fails about every 10 days, losing one day of updates for about 0.1% of user IDs — a loss the model barely notices.
Fast learning amplifies mistakes
A model that learns within minutes also learns from bots, from a broken client that logs every view as a skip, and from its own choices: it can only learn about videos it chose to show. Validate event streams before training, watch online metrics per model version, keep the last good snapshot ready to roll back to, and reserve exploration traffic so the model keeps seeing outside its own bubble.
How does a brand-new video — or a brand-new user — get a fair chance?
A video uploaded a minute ago has no watch history, so any model trained on engagement will rank it below videos with millions of proven views. If nothing is done, the rich get richer and new creators never break through. TikTok says that neither follower count nor a creator’s past hits is a direct factor in recommendations, which tells you new videos are meant to be judged on their own performance. How it does that is not public; here is a design you can defend.
Bad: rank new videos like any other. They lose to proven videos almost every time, and the few that get through do so by luck.
Good: give every new video some random impressions. Fair, but random viewers mostly don’t care, so the signal is noisy and the viewers are annoyed.
Great: staged exposure pools, targeted by content.
- Target the first pool. The video tower gives the new video an embedding from its frames, sound and caption. Pool 1 is about 200 viewers whose interests are close to it and who are currently in an exploration slot: a Tamil cooking video goes first to people who watch Tamil cooking videos, not to random strangers.
- Judge it against its peers. Live counters (from the event stream) track completion rate, rewatches, shares and “not interested” per view. The gate compares them with videos of the same category, length and language, not with global averages.
- Respect small samples. At 200 views, a 40% completion rate has a 95% interval of about ±7 points (1.96 × √(0.4 × 0.6 ÷ 200) ≈ 0.068). Smooth the rates towards the category average, and give borderline videos a second, slightly larger pool rather than a final verdict.
- Budget the exploration. 50M uploads × 200 first-pool views = 10B impressions a day — about 13% of our 75B views. That is why the fresh-pool slot is roughly one in eight, and why pool 1 cannot be much bigger.
- Check again before going wide. A video about to jump to hundreds of thousands of viewers is worth a second look; this is a natural point for human review of anything the classifiers were unsure about.
New users. TikTok says new users can pick interest categories; those who don’t get a feed of generally popular videos, and their first likes, comments and replays start the personalisation. In the design, the first batches are deliberately spread across many categories, live features update with every swipe, and the item-to-item source follows the first videos they finish. Device language and region help at first but, as TikTok notes, carry less weight than what people actually do.
How does the next video start instantly?
The target is a first frame in under 100 ms after a swipe. Over 4G, a fresh request for a video’s first bytes costs at least one round trip plus server time — easily 150–300 ms — before the decoder has anything to decode. The only way to beat that is to have the bytes on the phone before the swipe.
Bad: fetch on swipe. No wasted data, but every swipe shows a spinner on a slow network, which is fatal for a feed people swipe through every few seconds.
Good: download the next video completely in the background. Instant, but wasteful: many videos are skipped within two seconds, and at 21 Tbps of egress every wasted megabyte is real money and real data-plan cost for the user.
Great: prefetch only the head of the next few videos.
- Package for a fast start. Store each rendition as short fragmented-MP4 parts of about 2 seconds, with a smaller first part, so the player can start after one small download. Keep the cover image in the feed response so something is on screen in the first frame regardless.
- Start low, then climb. The prefetched head is a lower bitrate (say 540p at about 0.8 Mbps: 1.5 s ≈ 150 KB). After the swipe, adaptive bitrate switches up for the remaining parts while the first part plays.
- Prefetch depth depends on the network. Three heads on 4G, five on Wi-Fi, one with data saver on. The waste is small: about 450 KB of unwatched heads at the end of each session. At 8 sessions a day, 500M × 8 × 450 KB ≈ 1.8 PB a day, under 1% of the roughly 225 PB a day (2.6 TB/s × 86,400) the CDN already serves.
- Keep everything warm. A long-lived HTTP/2 or HTTP/3 connection to the CDN, a decoder initialised before the swipe, and the next batch requested when three videos remain, so the queue never empties.
- Pre-warm the CDN for what the recommender will push. Unlike a follow feed, the server chooses what millions of phones will request next. When a video is promoted to a larger pool, or starts trending in a region, push its first parts to that region’s edge caches, so the first wave of viewers doesn’t all miss to origin at once. (More in CDN.)
Tip
Measure time to first frame on the phone, split by “prefetched” and “not prefetched”. The first should be nearly flat at tens of milliseconds; if it isn’t, the problem is in decoding or the app, not the network.
How do we never show the same video twice?
TikTok says it doesn’t recommend content you have already seen. With 1,500 candidates per request and 110k requests a second, that is 165M membership checks a second against each user’s viewing history — so the check must be in memory and cheap.
Bad: query the full watch history per request. 13,500 IDs over 90 days per user, 1,500 lookups per request, against a database: far too slow.
Good: keep an exact set of recent IDs in memory. A per-user set of the last 7 days is exact and fast, but 90 days of exact sets is 54 TB, and anything older than the window can repeat.
Great: a Bloom filter per user. A Bloom filter answers “definitely not seen” or “probably seen”. Redis’s documentation gives the sizing: a 1% false-positive rate needs 7 hash functions and about 9.6 bits per item.
For 13,500 videos at 9.6 bits each, a user’s filter is about 16 KB, or about 8 TB for 500M users, small enough to load into the feed service’s memory when a session starts (shard sessions by user ID). Checking 1,500 candidates is 1,500 × 7 hash probes in local memory: well under a millisecond.
The trade-offs are specific. False positives wrongly drop about 1% of candidates — harmless when you have 1,500. False negatives never happen, so a seen video never slips through the filter itself. You can’t delete from a Bloom filter, so “forget after 90 days” needs time-sliced filters: one per 30 days, check the last three, drop the oldest each month. And decide what seen means: add a video when it was actually on screen for a moment, not when it was merely served, and keep the IDs served in this session in session state so two batches in flight can’t overlap.
| Exact set (90 days) | Bloom filter, 1% | Cuckoo filter | |
|---|---|---|---|
| Size per user | 108 KB | ≈ 16 KB | Similar to Bloom at low error rates |
| Wrongly dropped candidates | None | ≈ 1% | Small, tunable |
| Repeats shown | None | None | None |
| Delete an ID | Yes | No — use time slices | Yes |
| Best for | Short windows | Long history, cheap checks | When deletes matter |
How do we count views and likes at a million events a second?
Views arrive at 870k a second, and a viral video can take tens of thousands of views a second on its own. Updating a row per view would turn every hit video into a hot key. Instead, aggregate in the stream: a job keyed by video ID sums views, likes and completions over short windows (one second for display counters, a few minutes for ranking features) and writes one delta per video per window to a counter store. A video getting 50,000 views a second costs one write a second. Checkpointing the stream job and writing deltas keyed by window keeps replays from double counting.
Displayed numbers are rounded (“2.4M”) and may lag by a few seconds; nobody can tell. Likes themselves are idempotent rows keyed by (user, video), so “did I like this?” is exact while the count is batched — the same split as in design Instagram. Unique viewers, which creators see in analytics, use a HyperLogLog per video: with 16,384 registers the standard error is 1.04 ÷ √16,384 ≈ 0.8%, in about 12 KB. The same stream produces the live features the ranker reads, so counting and learning share one pipeline.
The final design
Three loops share one event stream: serving at the top, learning in the middle, media at the bottom.
- Ask for videos. When three videos remain, the phone asks the feed service for the next 8. The service holds the session’s served IDs and loads the user’s seen filter.
- Retrieve. Six sources run in parallel with 30 ms deadlines — two-tower ANN, item-to-item, following, trending, the fresh pool and exploration — and the seen filter drops anything already watched: about 1,500 candidates.
- Rank and mix. A pre-ranker cuts to 500; the multi-task model scores them with live features; re-ranking applies diversity, exploration, safety and ad slots. About 140 ms in total.
- Play. The phone plays from the CDN, starting from the prefetched first part, and prefetches the heads of the next videos.
- Report. Watch time, loops, skips, likes and shares go back in batches through the collector into the event log.
- Learn in seconds. Stream jobs update live features (this user’s recent videos, each video’s live completion rate) and display counters.
- Learn in minutes. Joined examples train the model online; touched embeddings sync to serving every minute, dense weights daily.
- Upload. A creator’s chunks go straight to object storage.
- Process. Transcoding, understanding and automated review run in parallel; renditions land in the video store, and a “video ready” event puts the video into the fresh pool for its first test audience.
Against the requirements: about 140 ms of server time per batch at p99; first frames from local bytes after a swipe, with under 1% of egress wasted on prefetch; behaviour reflected in features within seconds and in model parameters within a minute; new videos in front of a test audience about 10 minutes after upload, never before review; no repeats, at the cost of about 1% of candidates; and, if ranking fails, a cached or trending feed instead of an error.
What interviewers look for by level
| Area | Mid-level | Senior | Staff |
|---|---|---|---|
| Framing | A feed of videos | Recommendation-first; names the objective (watch time, not clicks) | Separates serving, learning and media loops; defines success including satisfaction and safety |
| Retrieval | “Query popular videos” | Multiple sources, two-tower embeddings, ANN | Index memory and freshness, popularity-bias correction, source quotas and deadlines |
| Ranking | One model scores everything | Funnel with pre-rank and rank, multi-task outputs | Latency budget per stage, value-model weights as product levers, re-rank rules |
| Learning | Retrain nightly | Real-time features, hourly training | Online training, serve-time feature logging, incremental sync, guardrails and rollback |
| Cold start | Show new videos randomly | Content embeddings for new videos | Staged exposure pools, small-sample statistics, exploration budget |
| Playback | Stream from a CDN | Prefetch the next video | Prefetch heads only, low-bitrate start, adaptive depth, CDN pre-warming driven by the recommender |
| Data structures | Store watch history | Bloom filter for seen videos | Time-sliced filters, “seen” semantics, stream-aggregated counters and HyperLogLog |
Follow-up questions
What if the ranking service is slow or down?
Degrade in steps instead of failing. If the heavy ranker misses its deadline, return the pre-ranker’s order; if retrieval is degraded, use whichever sources answered; if the whole recommender is down, serve a cached batch — keep the last few unserved candidates for each active session — or a regional trending list filtered by the seen filter. Protect the ranker with timeouts, a circuit breaker and load shedding, because a ranking fleet at 110M predictions a second at peak cannot absorb a retry storm. Users notice a slightly worse feed far less than a spinner.
What if users get stuck in a narrow or harmful loop of content?
Treat it as a ranking requirement, not a moderation afterthought. The re-ranker caps how many videos on one topic can appear in a window, reserves exploration slots, and applies stricter limits on sensitive topics, so a person who lingers on sad or extreme videos is not shown more and more of them. TikTok describes interspersing diverse content and avoiding repetitive patterns for this reason. Measure diversity per user as a tracked metric alongside watch time, and let users reset or steer recommendations.
What if we launch in a new country with no data?
Content embeddings carry over: a cooking video looks like a cooking video in any market. Start new users with interest picks and a locally trending feed, seed the fresh pool with local-language creators, and bootstrap user and video towers from a model trained elsewhere, then let online training adapt within days. Keep the exposure budget per country so a small market’s new creators aren’t crowded out by imported hits.
What if a bad model update ships?
Because online training changes the model every minute, ship training code and configuration like any risky deployment: run the new version on a small slice of traffic, compare watch time, skips, “not interested” and diversity against the control, and roll back to the last good snapshot automatically if guardrails trip. Monolith’s daily snapshots make rollback cheap. See canary deployments.
What if one video goes viral in one region?
The recommender knows first, because it is the one promoting the video. Push its first parts to that region’s edge caches as it moves to a bigger pool, use an origin shield so a miss at many edges becomes one fetch from storage, and let the stream-aggregated counters absorb tens of thousands of views a second as one write per window. Re-check safety before the next promotion: the cost of a mistake grows with the pool.
How to say it in the interview
“I’ll treat TikTok as a recommender with a video player attached: about 500 million daily users, 150 videos each, so 870 thousand views and around 110 thousand feed requests a second.
Serving is a funnel. Each request retrieves about 2,000 candidates in parallel from several sources: a two-tower model whose video vectors sit in a sharded approximate nearest-neighbour index, item-to-item from what you just finished, creators you follow, regional trends, a pool of new videos under test, and some exploration. A per-user Bloom filter drops anything you’ve seen. A small model trims to 500, a multi-task model predicts completion, watch time, likes, shares and skips, and a re-ranker enforces diversity, safety and exploration before returning 8. That’s about 140 milliseconds.
Learning happens at three speeds. Live features update within seconds from the event stream. Following ByteDance’s Monolith paper, a joiner pairs actions with the features we logged at serve time, an online trainer updates embeddings, and only touched embeddings sync to serving every minute; dense weights sync daily.
New videos get a content embedding at upload and go through staged test pools — a couple of hundred viewers, then thousands — judged against similar videos, after automated review. New users start with interest picks or popular videos and adapt within the session.
Playback never waits: the phone prefetches only the first second and a half of the next three videos at a low bitrate, starts from local bytes on a swipe, then ramps up quality. Because the recommender decides what will be popular, it pre-warms CDN edges before the wave arrives.
Counts are aggregated in the stream, so a viral video costs one write per window.”
Concepts used in this design
- Embeddings and semantic search — user and video vectors and nearest-neighbour retrieval.
- Vector databases — HNSW and IVF-PQ indexes, sharding and freshness.
- Kafka — the event log that serving, counting and training all read.
- Flink and stream processing — streaming joins, windows and live features.
- Batch vs stream processing — why the model learns from a stream, not a nightly job.
- Bloom filters — never repeating a video with 16 KB per user.
- Count-min sketch and HyperLogLog — unique-viewer counts in a few kilobytes.
- Content delivery network — edge caching, origin shields and pre-warming.
- Large file uploads — resumable, direct-to-storage uploads.
- Hot keys, cache stampede and invalidation — viral videos as hot keys for counters and caches.
- Backpressure and load shedding — degrading the feed instead of failing it.
- Canary, blue-green and feature flags — rolling out model versions safely.
- Strong vs eventual consistency — exact like rows, approximate counts.
References and further reading
- How TikTok recommends videos #ForYou (TikTok Newsroom, 2020)newsroom.tiktok.comSignals used for ranking, finishing a video as a strong signal, interest picks for new users, no repeats or same creator/sound back to back, and new or under-review videos being ineligible.
- Thanks a billion! (TikTok Newsroom, September 2021)newsroom.tiktok.comMore than 1 billion people use TikTok every month.
- Content moderation (TikTok Safety Center)tiktok.comAutomated review before content is posted, For You eligibility standards, and the share of violating videos caught by automation in 2025.
- Monolith: Real Time Recommendation System With Collisionless Embedding Table (ByteDance, 2022)arxiv.orgOnline training with Kafka and a Flink joiner, cuckoo-hash embedding tables with admission filters and expiry, minute-level sync of touched embeddings, and daily snapshots.
- Deep Neural Networks for YouTube Recommendations (Covington et al., RecSys 2016)static.googleusercontent.comThe candidate-generation then ranking split (millions → hundreds → dozens), nearest-neighbour serving, the example-age feature for freshness, and ranking by watch time rather than clicks.
- Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations (Yi et al., RecSys 2019)research.googleTwo-tower retrieval trained with in-batch negatives, the popularity bias that creates, and a streaming frequency estimate to correct it.
- Faiss wiki: Guidelines to choose an indexgithub.comHNSW memory per vector, product-quantisation code sizes, and IVF settings for 100M to 1B vectors.
- Bloom filter (Redis documentation)redis.ioBits per item and hash functions for a given false-positive rate (9.6 bits and 7 hashes for 1%).