In December 2014 the view counter under Gangnam Style ran out of room. The video had passed
2,147,483,647 views, the largest number a signed 32 bit integer can hold, and YouTube had to
move the counter to 64 bits. Nobody had planned for one video to be watched more than two
billion times. That is the kind of problem this chapter is about.
YouTube / at a glance
You already know how to store and stream the video file itself. That is the
video streaming chapter: upload in parts,
transcode in chunks, serve segments from a CDN. This chapter is everything around the player.
The number under the video, the subscriptions tab, the bell, the comments, and the copyright
check that runs before anyone sees an upload. In an interview, "Design YouTube" is mostly
these parts, and they are where the hard problems sit.
"Design YouTube" is huge, so the first job is to agree on which part you are building. Ask
these in the first three minutes.
You ask
They say
What it settles
Is the video pipeline part of this, and the home page recommendations?
No. Assume upload, transcoding and playback exist. Treat the home page ranking as a service another team owns.
You can spend the round on the platform around the player. Say in one sentence how the pipe works and point at it, so the interviewer knows you are cutting it on purpose.
How big?
Assume a billion watch page loads a day. Some channels have hundreds of millions of subscribers.
The watch page is a read problem a cache solves. The big channels are what break a simple subscriptions feed, so they get their own deep dive.
How exact does the view count have to be?
Close enough, within a few minutes, for viewers. Exact, with fake views removed, for paying creators.
Two numbers from two pipelines: a fast one for display and a slow, careful one for money. This single answer shapes the whole counting design.
Who gets told when a channel uploads?
Every subscriber sees it in their subscriptions tab. Only those who turned the bell on get a push notification, within minutes.
Two different audiences of two different sizes. The feed can be slightly late, the push can be spread over a few minutes, and neither has to be instant.
How are comments ordered?
Top comments by default, newest first as an option.
Top order cannot be a sort at read time on a video with a million comments. It becomes a list you compute ahead of time.
Do we check uploads for copyright?
Yes. Match each upload against reference files that rights holders have supplied, before it goes public.
A gate in the publish path that can block a video or hand its money to someone else. How the matching works is its own system, so you design the gate, not the matcher.
Move the sliders. Watch which number grows fastest.
Where the load is
1 billion
6
3
Watch pages per second1 billion / 86,400 = 11,574 /s
At the evening peakaverage x 3, an assumption = 34,722 /s
Heartbeats per secondone every 30s for 6 min = 138,889 /s
Likes per second3 per 100 views = 347 /s
138,889 /s
heartbeat events, and every one is a write
The watch page is 11,574 reads a second, and nearly all of them are the same few thousand popular videos, so a cache absorbs it. The heartbeats are 400 times the likes and every one is a write. That stream must never touch a database row directly, which is what the first deep dive is about.
The number people get wrong
Candidates average the views across all videos and conclude a counter column is fine. The
average hides the shape. Ten million views of one trailer in its first hour is about 2,800
a second, all on one row. A single row in a relational database manages a few thousand
updates a second at best before the lock queue becomes the bottleneck, and that is one
video on one good day.
Why: Identical for every viewer, so it can be cached for a minute anywhere, including at the CDN. The counts in it are the approximate display numbers, and saying so out loud is part of the answer.
Why: The few personal fields, in their own tiny call. Put "did you like this" into the main response and every response becomes unique to one viewer, which makes the busiest endpoint you own uncacheable.
PUT/v1/videos/{id}/rating
{ "value": "like" }
returns 204
Why: PUT sets a state rather than adding one. A double tap, a retry on a bad network, or two open tabs all leave the same single row, so they cannot inflate the count.
PUT/v1/channels/{id}/subscription
{ "notify": "all" }
returns 204
Why: The bell is a field on the subscription, not a separate feature. The notification fan out reads only the subscribers whose field says "all", which is a much smaller set than everyone.
GET/v1/videos/{id}/comments?order=top&cursor=...
returns 200 { comments: [...], nextCursor }
Why: A cursor, not a page number. New comments arrive while you scroll, and an offset would show you the same comment twice. Views come from the heartbeat endpoint in the video chapter, so there is no "add a view" call here at all.
Three tables carry the platform. The first one shows the most important idea in this
chapter: the row is the truth, and the count is something you work out from the rows.
ratingsMySQL, sharded by video_id
video_id
bigint
PK
Shard key, so every rating for one video lives together.
user_id
bigint
PK
One row per viewer per video. This is what makes a double like impossible.
value
enum
like, dislike or none. Unliking sets none rather than deleting, so the change is visible to the counting pipeline.
updated_at
timestamp
Sample row
88123 | 5521 | like | 2026-10-19 18:02:11
There is no like_count column anywhere. When a rating actually changes (none to like, like to none), the same transaction writes a +1 or -1 event to the counting pipeline. A repeated PUT that changes nothing writes no event, which is how the count stays honest.
subscriptionsMySQL, stored twice
subscriber_id
bigint
PK
The copy sharded by subscriber answers "which channels do I follow".
channel_id
bigint
PK
The copy sharded by channel answers "who follows this channel", which the fan out reads.
notify
enum
IDX
all, personalized or none, the three bell settings YouTube offers. Indexed with channel_id so the bell set is one range scan.
created_at
timestamp
Two copies, because the two questions shard in opposite directions. A single table sharded by subscriber would make every upload a query to every shard. Keep them in step by writing the second copy from an event, and accept a second or two of lag.
commentssharded by video_id
video_id
bigint
PK
Shard key. Every comment on a video is on one shard, so loading a page of them is one query.
comment_id
bigint
PK
Time ordered, from the unique ID generator, so "newest first" is a plain range read.
parent_id
bigint
IDX
Null for a top level comment. Replies go one level deep, which keeps a thread to two reads.
author_id
bigint
body
text
state
enum
visible, held for review, removed. Hiding a comment is a state change, never a delete.
Like counts on comments come from the same counting pipeline as video likes. The top comments order is not a column either. It is a list computed in the background, covered in the third deep dive.
What YouTube actually ran
YouTube kept its metadata in MySQL, and around 2010 a single database could no longer
hold it. The team built Vitess, a layer that splits MySQL across many shards and sends
each query to the right one, then open sourced it. It is now a CNCF graduated project
used far outside Google. So "sharded MySQL" here is not a compromise, it is the real
history.
Figure 1 has three paths. The top row runs once per upload. The middle row runs a billion
times a day. The bottom row is where the counts come from.
transcoded1published2small channels3bell only4watch page56on a miss7on a miss8subs tab9heartbeatsevery minuteUpload pipelinethe video chapterContent IDmatch before publishFan out workerssmall channels onlyNotificationsbell subscribersSubscription feedsids per viewerVieweropens a videoWatch APIshared + personalWatch page cachemetadata + countsVideo metadatasharded MySQLCommentssharded by videoCounting pipelineviews and likes
transcoded1published2small channels3bell only4watch page56on a miss7on a miss8subs tab9heartbeatsevery minuteUpload pipelinethe video chapterContent IDmatch before publishFan out workerssmall channels onlyNotificationsbell subscribersSubscription feedsids per viewerVieweropens a videoWatch APIshared + personalWatch page cachemetadata + countsVideo metadatasharded MySQLCommentssharded by videoCounting pipelineviews and likes
Figure 1. Nothing on the watch path writes to a database. Counts arrive in the cache from the counting pipeline, and subscription feeds are filled in ahead of time by the publish path along the top.
Walking Figure 1:
The upload pipeline from the video chapter finishes transcoding and hands the video on.
It is still private.
Content ID compares the video's audio and picture fingerprints against the reference
files rights holders have supplied. A match applies the owner's chosen policy: block
the video, run ads and pay the owner, or just track it. Only then is the video
published.
Fan out workers write the new video's id into the subscription feed of every subscriber,
for channels below a size threshold. Big channels skip this step (the second deep dive).
Subscribers with the bell on get a push notification, sent out over a few minutes
rather than all at once.
A viewer opens a video. The watch API makes two calls in parallel: the shared part,
which is the same for everyone, and the small personal part.
The shared part comes from a Redis cache. For a popular video it is always there.
On a miss, the cache loads the video's row from the metadata shard that owns it.
Comments are loaded the same way, but only when the viewer scrolls down to them.
The subscriptions tab reads the viewer's own feed list, filled in ahead of time by step 3.
The two dashed lines are the counting path. The player sends a heartbeat every 30 seconds,
heartbeats and like changes go into the counting pipeline, and about once a minute the
pipeline writes fresh counts into the cache. Writes like a new comment or a rating go
through the watch API to their own tables, and are left off the figure so the read path
stays readable.
Your answer
Figure 1 never adds one to a view count in a database. Where does the number under the video actually come from, and what would go wrong if it were a column you updated on every view?
Start with the obvious design, because the interviewer wants to hear you reject it. Every
view runs UPDATE videos SET views = views + 1. Each update locks the row, so updates to
one video queue up behind each other. On a normal video that is fine. On a trailer at
2,800 views a second, the queue grows faster than it drains.
Compare three places the +1 can go.
Where the +1 goes
1lock, add, release2PlayerAPIvideos rowviews + 1
1lock, add, release2PlayerAPIvideos rowviews + 1
Simple, exact on paper, and it falls over on the first viral video, because every view of that video waits for the same lock. It also counts whatever arrives, including a bot reloading the page in a loop. Nothing here can tell a real view from a fake one.
The log design gives you two numbers, and you should say so before you are asked. The
display count is fast and roughly right: the stream job drops obvious repeats, such as
the same session counted twice in a minute, and moves on. The payout count is slow and
exact: the daily job has the whole day of events, so it can remove a session that watched
400 videos in an hour, or ten thousand views from one block of addresses. When the daily
number is ready it replaces the display number. It is the same split as the
ad click aggregator, for the same reason: money
needs a careful count, and a screen needs a quick one.
What counts as a view? YouTube does not publish its rule, and you should not pretend to
know it. A defensible answer: a view is a session whose heartbeats show at least 30
seconds of playback, or the whole video if it is shorter. Then say it is a product
decision you would make configurable, because it will change.
The 301 views
For years, new videos on YouTube appeared to freeze at around 301 views while the real
number climbed. The public count was held while YouTube checked that early views came
from people, then released once the check finished. It is the two numbers idea, visible
on the page, and in 2015 YouTube changed the system so counts updated more often.
There is one more trap inside the log design. Kafka spreads events over partitions using a
key, and the natural key is the video id, because then one stream worker sees every event
for a video and can add them up. Watch what happens to that key when one video gets most of
the traffic.
Partition key
13
12
13
12
13
12
13
12
0
1
2
3
4
5
6
7
Partition 2 carries 13.3% (even would be 12.5%)
Millions of videos hash across eight partitions and the load comes out even. Every worker does about the same work. This is why video_id looks like a perfect key in testing.
Salt every key, or only the hot ones? Salting every video costs the extra adding step on
millions of videos that get a few views a day. A common answer is to salt only videos the
pipeline has seen go past a rate threshold in the last few minutes. I have not run this at
YouTube's scale, so treat the exact threshold as something you would measure.
Subscriptions: one upload, 250 million subscribers#
The subscriptions tab is a news feed, and the
news feed chapter already gives the answer: write the new
post into each follower's list (push), except for accounts so big that the writes would take
too long, which readers pull in when they open their feed. YouTube has more of those big
accounts than most networks. India is YouTube's largest audience, and a few channels,
T-Series among them, have passed 250 million subscribers. A channel like that cannot fan out
with one write per subscriber.
YouTube adds one twist the plain news feed does not have: the bell. Here is one upload from
a channel that size, step by step.
PublishFan outNotificationsViewerFeedsvideo published, 250M subscribersabove threshold: no per subscriber writesbell subscribers onlypushes spread over 10 minutesopens the subscriptions tabmerged by publish time
1/6 The publish event carries the channel id. The fan out service looks up the subscriber count first, because the count decides which path the upload takes.
Figure 2. The subscriptions tab and the bell are two separate audiences. The tab can be filled in lazily. The bell is a smaller set, and its real danger is not sending pushes but what happens when everyone taps them at once.
The follow up you will get
"How do you pick the threshold?" Do not guess a number and defend it. Say what it trades:
below it you pay write work at upload time, above it you pay merge work on every read of
the subscriptions tab. Start around 100,000 subscribers, then measure fan out lag and tab
read latency and move the line until neither is the problem. Being able to move it
without a deploy is the real requirement.
A million comments on a video sounds like a write problem. It is not. A million comments in
a day is about 12 a second, which one shard handles without noticing. The problem is the
read: everyone who opens the video and scrolls down asks for the same first page of top
comments, and "top" means sorted by a score that keeps changing.
Top comments
12Viewer scrollsORDER BY scoreover 1M rowsFirst 20
12Viewer scrollsORDER BY scoreover 1M rowsFirst 20
Always fresh, and it reads a million rows to return twenty, for every viewer, on the one shard that holds this video. On a busy video that shard spends its whole day sorting the same comments.
Your own new comment is the one exception. If you post a comment and do not see it, you post
it again. So the API puts the viewer's own recent comments at the top of their view, from the
write it just made, even though the ranked list does not contain them yet. This is the same
"read your own writes" fix the consistency article
describes, done in the API instead of the database.
A big film trailer goes live at noon on a channel with a lot of bell subscribers. Drag the
dial and watch which box goes first.
Trailer launch
normal noon
normal noonpushes in one burstcache entry expirescounter frozenrepaired
pushmissheartbeatsNotificationsViewersWatch APIWatch page cacheMetadata shardowns this videoCounting pipeline
pushmissheartbeatsNotificationsViewersWatch APIWatch page cacheMetadata shardowns this videoCounting pipeline
Healthy. The cache answers nearly every watch page. The metadata shards see a trickle of misses from the long tail of old videos. Counts lag by about a minute.
Read every step as text
normal noon · Healthy. The cache answers nearly every watch page. The metadata shards see a trickle of misses from the long tail of old videos. Counts lag by about a minute.
pushes in one burst · Degraded. All bell notifications go out in the same minute. Millions of taps arrive before the watch API can add servers, so request latency rises. The cache is fine, because it was filled at publish. Spreading the pushes over ten minutes removes this spike.
cache entry expires · Down. The trailer’s cache entry hits its expiry time at the peak. Every API server misses at once and sends the same query to the one shard that owns the video, thousands of times a second. That is a cache stampede. The fix is to let one request per key refill the cache while the others wait, and to refresh hot keys before they expire.
counter frozen · Degraded. Heartbeats are keyed by video id, so the trailer’s whole stream lands on one partition. That partition falls twenty minutes behind and the view count stops moving on the most watched video of the day, which is exactly where people look. Salting the hot key spreads it out.
repaired · Healthy. Pushes spread over ten minutes, one refill per key with early refresh for hot entries, and salted keys for hot videos. None of the fixes adds a new box. Each one changes when or where work happens.
The cache stampede is the one to raise without being asked. It only happens on your most
popular video at its most popular moment, so it is the outage everyone sees.
The whole prompt. They are waiting to see what you cut.
You
The video pipe is a known shape: upload in parts to object storage, transcode in chunks, serve segments from a CDN. I would describe it in a minute and then spend the round on the platform around it, because that is where the interesting load is: the view counter, the subscriptions feed for very large channels, and comments. I would treat recommendations as a service I call. Does that split work for you?
Scopes out loud and asks for agreement. The interviewer can redirect now instead of at minute 30.
Interviewer
Fine. How do you count views?
The question they came for.
You
Not with an UPDATE. A trailer can get thousands of views a second on one video, and one row cannot take that. The player sends a heartbeat every 30 seconds into Kafka. A stream job adds them up per video in one minute windows and writes the total into the cache, so it is one write a minute per video. The raw events are kept, and a daily job recounts them with deduplication and fraud filtering. That second number is the one creators are paid on, and it replaces the display number when it lands.
Rejects the naive design with a number, then gives both pipelines and says which one money uses.
Interviewer
What exactly counts as a view?
A test of whether you will make something up.
You
I do not know YouTube’s actual rule, they do not publish it. I would start with a session that plays at least 30 seconds, or the whole video if it is shorter, and make the threshold a setting. It is a product decision, and the heartbeat data lets us change it later and recount.
Says "I do not know" plainly, then gives a usable default and the reason it can change.
Interviewer
A huge channel uploads. What happens?
Checking for the celebrity problem.
You
Above a threshold we skip writing into every subscriber’s feed. The video goes into the channel’s own recent uploads list, and when a viewer opens their subscriptions tab we merge their feed with the recent uploads of the few big channels they follow. Bell subscribers are a separate, smaller set. We push to them, but over ten minutes, because if everyone taps in the same minute the watch page takes the hit. And we fill the cache before the first push.
Reuses the news feed answer, then adds the bell and the stampede on the watch page, which is the YouTube specific part.
Interviewer
The view count on today’s biggest video stopped moving. Everything else is fine. Why?
A debugging question. One video broken, everything else fine, is a strong clue.
You
One video broken while others are fine points at something keyed by that video. Most likely its heartbeats all hash to one Kafka partition, and that partition is behind. I would check consumer lag per partition. If one partition is far behind and the rest are not, that is it, and the fix is to salt the key for hot videos so they spread across several partitions and add the parts up afterwards.
Reasons from the symptom to a cause, names how to confirm it, then gives the fix.
Push below the threshold, pull above it, and spread bell pushes over about ten minutes. The stampede is on the watch page, not the notification service.
Say this in 60 seconds
I would cut the video pipe early, since upload, chunked transcoding and CDN delivery are a known design, and spend the time on the platform around the player. The watch page is split into a shared part that caches for everyone and a tiny personal part, so a billion loads a day mostly hit a Redis cache in front of sharded MySQL. View counts never touch a database row: the player sends heartbeats into Kafka, a stream job adds them up per video every minute and writes the total to the cache, and a daily job recounts the raw events with fraud filtering for creator payouts. Hot videos get a salted key so one trailer does not swamp one partition. Likes are one row per viewer, set with a PUT, and a change emits a plus or minus one event into the same pipeline. Subscriptions are a news feed: push into feeds for small channels, pull at read time for channels with hundreds of millions of subscribers, and send bell notifications over ten minutes so the watch page does not get a stampede. Top comments are a list ranked in the background, not a sort at read time. Content ID sits in the publish path as a gate. The failure I would call out is the trailer's cache entry expiring at its peak, so hot keys refresh early and only one request per key refills the cache.