How YouTube keeps comment counts accurate at scale
How YouTube maintains accurate comment counts on videos with millions of comments using distributed counters, eventual consistency, and anti-spam reconciliation.
The Problem Statement
Interviewer: "A viral YouTube video is getting 50,000 new comments per second. You need to display an accurate comment count next to the video. How do you keep that counter accurate without melting your database?"
This question tests three things: your understanding of write contention on a single database row, your knowledge of distributed counter patterns like sharded counters, and whether you can articulate the tradeoff between perfect accuracy and system availability at massive scale.
This question looks trivially simple on the surface. "Just increment a counter, right?" But the moment you try to increment a single row at 50K writes per second, you discover why large-scale systems use a different approach. The naive solution is the one that breaks production.
Counting is one of the hardest problems in distributed systems. Not because the math is hard, but because single-row mutations at high throughput create lock contention that cascades into timeouts, retries, and eventually an outage. Understanding this is what separates a textbook answer from a production-ready one.
Clarifying the Scenario
You: "Good question. Let me scope this before I dive in."
You: "When you say 'accurate,' are we talking about exactly right at every millisecond, or within a few seconds of the true count? Those lead to very different architectures."
Interviewer: "Eventually consistent is fine. Within 5 seconds of the true value."
You: "Got it. And is this just comment counts, or should I also handle like counts, view counts, and reply counts?"
Interviewer: "Focus on comment counts, but I want to see a pattern that generalizes."
You: "One more: should I handle the case where comments are deleted or marked as spam? That means decrements, not just increments."
Interviewer: "Yes, include that."
You: "OK. I will structure my answer in three parts: why a single-row counter breaks at scale, sharded counters that spread writes across many rows, and the anti-spam reconciliation flow that handles the count going down when spam is removed."
A Sharded-Counter Approach
I break this into five parts:
- Why single-row counters fail: Lock contention on a single row turns 50K writes/second into a serialized bottleneck where each write waits for the previous one to release the row lock
- Sharded counters: Instead of one counter row, create N counter shards. Each write picks a random shard and increments it. Reads sum all shards
- Periodic rollup: A background job sums all shards into a snapshot, then resets them. This keeps reads fast and prevents shard proliferation
- Anti-spam reconciliation: When spam is detected retroactively, the count must go down. This introduces negative shard values, batch re-counts, and timing challenges
- Eventual consistency and read divergence: Why two users looking at the same video see different counts, and why that is acceptable
The mental model is straightforward: spread writes across many rows to avoid contention, then consolidate reads with background aggregation. This pattern appears in systems such as DynamoDB, Bigtable, and Spanner, as well as in many large-scale counter designs.
YouTube processes over 500 hours of video uploaded per minute and billions of comments per day. A single viral video can receive 50,000+ comments per second during a live event. At this scale, even a well-indexed single-row counter creates a write hotspot that cascades into broader database contention.
The Architecture
Here is the full counter architecture showing how writes fan out across shards and how reads reconstruct the count.
The walkthrough:
-
User A posts a comment. The write handler picks a random shard (say shard 2) and increments it by 1. There is no lock contention because shard 2 is only one of N shards, so the probability of two writes hitting the same shard simultaneously is 1/N.
-
User C reads the comment count. The read handler fetches the snapshot value (9,847,231, consolidated 5 seconds ago) and adds the sum of all active shards (+14 +9 +11 -2 +7 = +39). It returns 9,847,270.
-
The rollup job fires every 5 seconds. It sums one active shard generation (+39), adds that to the snapshot (9,847,231 + 39 = 9,847,270), writes the new snapshot, and atomically rotates to a fresh generation so increments arriving during the rollup are not lost.
-
Spam detection removes comments. When the ML classifier flags a comment as spam, the system decrements a random shard by 1. Shard 4 shows -2 because two spam comments were removed since the last rollup. The math works cleanly because the rollup sums all values, positive and negative.
For the interview: say you would use N=64 shards for a hot video. At 50K writes/sec, each shard sees about 780 writes/sec, which is well within the throughput limit of a single Bigtable or Spanner row.
Sharded Counters and the Hot-Key Problem
This is the core of the solution and the part that distinguishes a strong answer from a weak one. The hot-key problem is simple: when many concurrent writes target the same database row, they serialize on the row lock. At 50K writes/sec, each write waits for the previous lock to release. Effective throughput drops to maybe 1,000 to 2,000 writes/sec, and everything else queues up, times out, and retries, creating a cascading failure.
Sharded counters solve this by splitting the single hot key into N independent keys. Each write picks one shard at random and increments only that shard. The write throughput is N times higher because contention is divided by N.
The key design decisions:
How many shards? Too few and you still have contention. Too many and reads become expensive (summing more rows). Start with 64 shards for high-traffic videos and 4 shards for normal videos. Dynamically adjust based on write rate: a background monitor detects high write latency and doubles the shard count. When traffic subsides, halve them.
Random vs hash-based shard selection? Random distributes writes more evenly, which is what we want for counters. Hash-based (on request ID or user ID) is deterministic but can produce skew if certain hash buckets are overrepresented. Use random.
What storage system? Bigtable and Spanner are natural fits because they handle high write throughput per row. DynamoDB works with its atomic increment operations. Redis is fast but not durable by default, so a restart loses the count. Use a durable store for the authoritative counter, Redis as a read cache.
The rollup. A background job runs every 5 seconds: read one active shard generation, sum it, add the sum to the snapshot row, and rotate to a fresh generation. Between rollups, the read path computes snapshot + SUM(active shards). Maximum staleness is bounded by the rollup interval when the job is healthy, but recovery and cache delays can add more.
Rollup atomicity and convergence
A naive read-then-reset can lose an increment that arrives between the read and the reset, and two rollup workers can apply the same generation twice. Use a store-supported transaction, a compare-and-set version on the snapshot, or generation rotation: claim one generation, write the consolidated snapshot with its version, and mark that generation closed exactly once. If a worker crashes before the snapshot is committed, leave the generation available for retry; if it crashes after a successful commit, the version check prevents a duplicate application.
Readers can batch-fetch the snapshot and active shards. A periodic reconciliation against the visible-comment source of truth remains necessary because the counter and comment write are separate operations.
A common interview mistake: "Just use Redis INCR." Redis handles 100K+ increments per second on a single key, which sounds sufficient. But Redis is single-threaded per shard, so a hot key blocks other operations on that shard. More importantly, Redis is not durable by default. A restart loses the count. Sharded counters in a durable store (Bigtable, Spanner) are the correct production answer. Use Redis as a read cache, not as the primary counter.
Anti-Spam Reconciliation: When Counts Go Down
Here is where it gets interesting. Comments do not only accumulate. They also get deleted, flagged as spam by ML classifiers, removed by moderators, or retracted by the author. Every removal must decrement the counter, and the timing of that decrement creates subtle consistency challenges.
The anti-spam pipeline runs asynchronously. An ML classifier evaluates each new comment and may flag it as spam minutes, hours, or even days after it was posted. When a batch of comments is removed, the counter must be adjusted downward.
The decrement path works just like the increment path but in reverse. When the spam classifier flags a comment, the system picks a random shard and decrements it by 1. The shard can go negative, which is fine. The rollup sums all values (positive and negative) into the snapshot.
Batch removal is more interesting. When the ML classifier does a sweep and flags 10,000 comments as spam at once, you have two choices: issue 10,000 individual decrements (one per shard pick), or issue a single decrement of -10,000 to one shard. The batch approach is more efficient but requires the classifier to know the exact count being removed. I prefer the batch approach with a safeguard: the reconciliation job catches any discrepancies.
The reconciliation job is the safety net. It runs hourly for popular videos, daily for others. It queries the comments table directly: SELECT COUNT(*) FROM comments WHERE video_id = 'abc' AND status = 'visible'. If the counter value differs from the actual row count by more than a threshold (say, 0.1%), the reconciliation job writes a correction delta to a shard.
For an interview, mention the reconciliation job proactively. It shows that distributed counters can drift from reality due to race conditions, crashed processes, or double-counting. The reconciliation job is the "source of truth audit."
The reconciliation job is often the missing detail in interviews. Sharded counters can drift, so reconcile periodically against the source of truth to correct failed increments or race conditions.
Eventual Consistency and Why Your Count Differs from Mine
Here is the part that trips up candidates who think "eventually consistent" means "sloppy." Eventual consistency is a deliberate architectural choice that buys you 1000x write throughput. Two users looking at the same video at the same second may see different comment counts. This is by design, and it is perfectly acceptable.
There are five reasons counts diverge between users:
CDN caching. The comment count endpoint is cached at CDN edge locations with a 2 to 5 second TTL. User A in New York hits a CDN PoP with a cache entry from 3 seconds ago. User B in London hits a different PoP with a cache entry from 1 second ago. They see different counts.
Rollup timing. The rollup fires every 5 seconds. Between rollups, the read path sums snapshot plus active shards. If User A reads 1 second after a rollup and User B reads 4 seconds after, the shard values have accumulated differently.
Database replica lag. Reads may hit different database replicas with slightly different replication states. Even with strong consistency on the primary, read replicas lag by 10 to 100ms.
Spam filter timing. The ML classifier runs asynchronously. User A might see the count before a spam batch is removed. User B reads after the removal. The count went down for User B but not for User A yet.
Read-your-own-writes for the commenter. When you post a comment, you should see the count go up by 1 immediately. Other users do not see your comment reflected until the next rollup.
The read-your-own-writes pattern deserves specific attention. When User A posts a comment, store a per-session adjustment in a fast cache (Redis or in-memory on the app server). When User A reads the count, the app server adds the adjustment only when the base snapshot or shard read may not include the write. Give the adjustment a TTL slightly longer than the rollup interval (for example, 10 seconds for a 5-second rollup), and clear or reconcile it once the write is visible. A fresh snapshot plus active-shard read must not add the same increment twice.
This read-your-own-writes technique works for eventually consistent counter systems when the session adjustment is scoped and deduplicated against the base read.
The negative count bug. Here is a subtle edge case: if spam removal decrements the count below zero, you should floor it at 0 in the display layer. A race between a comment addition and spam batch deletion can temporarily produce a negative shard sum. The storage layer allows negative values (necessary for correct math), but the API layer should never return a negative comment count. Add a MAX(0, computed_count) guard in the read path.
The "views" counter on YouTube uses a different approach than comments. Views are counted approximately using HyperLogLog (probabilistic cardinality estimation) because exact deduplication of billions of views is prohibitively expensive. Comment counts need to be exact (eventually) because users mentally verify them: "I posted a comment, the count should go up by 1." Views are too large for anyone to notice a 0.1% error.
The Tricky Parts
-
Counter drift after crashes. If a shard increment succeeds but the comment write fails (or vice versa), the counter drifts from reality. A safer ordering is to write the comment first, then increment the shard; if the increment fails, the reconciliation job catches the discrepancy. This makes the counter slightly lag reality rather than lead it.
-
Hot video shard rebalancing. A newly viral video starts with 4 shards. Once it goes viral, 4 shards cannot handle 50K writes/sec. The system needs a "shard expansion" mechanism: a background monitor detects high write latency and doubles the count. Old shards remain active (rolled up normally). New writes go to the expanded set. This must be seamless with no counter loss.
-
Comment count vs reply count vs thread count. A product may display multiple counters per video. Each needs its own shard set. Reply counts need per-parent-comment sharding. A compound shard key works:
video:abc:shard:7for top-level comments,comment:xyz:shard:3for replies to a specific comment. -
Displaying counts across time zones and CDN caches. Users in different regions see different counts because they hit different CDN PoPs with different cache ages. A short TTL (2 to 5 seconds) on count endpoints minimizes divergence while still protecting the counter store.
-
The reconciliation query is expensive.
SELECT COUNT(*) WHERE video_id = ? AND status = 'visible'can be slow for videos with millions of comments. A secondary exact counter, updated in a database transaction alongside the comment insert/delete, can serve as the reconciliation source of truth. This trades write complexity for fast reconciliation reads.
What Most People Get Wrong
| Mistake | What they say | Why it is wrong | What to say instead |
|---|---|---|---|
| Single-row counter | "Just increment a counter column" | Row lock contention at 50K writes/sec causes cascading timeouts | "Shard the counter across N rows, each write picks a random shard" |
| Redis as primary store | "Use Redis INCR, it handles 100K ops" | Redis is not durable, a restart loses the count, hot key blocks the shard | "Durable store (Bigtable, Spanner) for sharded counters. Redis as a read cache" |
| Ignoring decrements | "Just increment on new comments" | Spam removal, moderation, and user deletions make the count drift upward | "Shards support negative values. Deletes decrement a random shard. Reconciliation corrects drift" |
| Over-engineering consistency | "Use distributed transactions for every increment" | Transactions kill throughput at this scale | "Accept 5-second eventual consistency. Read-your-own-writes for the commenter" |
| No rollup strategy | "Sum all shards on every read" | 64+ shard reads per request at 10M reads/sec overwhelms the store | "Periodic rollup consolidates into a snapshot. Reads are snapshot plus active delta" |
How to Communicate This in an Interview
Here is a concise way to say this:
"The core problem is lock contention. If 50,000 comments per second all try to increment the same database row, you get a serialized bottleneck. Each write waits for the row lock. Effective throughput drops to maybe 2,000 writes/sec, and everything else queues up and times out.
The solution is sharded counters. Instead of one counter row per video, I create 64 counter shards. Each comment write picks a random shard and increments it. This spreads 50K writes across 64 rows, so each row sees about 780 writes/sec, well within tolerance.
For reads, I do not sum all 64 shards every time. A background rollup job runs every 5 seconds, sums all shards, writes the total into a snapshot row, and resets the shards. Reads return snapshot plus active shard delta. The count is at most 5 seconds stale.
For the commenter, add read-your-own-writes: store a per-session adjustment so they see their own comment reflected immediately, even before the next rollup.
Spam removal is the interesting wrinkle. When the ML classifier flags comments retroactively, the system decrements a shard just like it increments one. Shards can go negative. An hourly reconciliation job compares the counter against the actual comment row count and corrects any drift."
Interview Cheat Sheet
- Trigger: "Counter at scale" or "How would you count X" say "sharded counters with periodic rollup."
- Hot-key problem: "A single counter row becomes a write bottleneck due to row-level locking. Shard the counter across N rows."
- Shard count: "64 shards for hot resources, 4 for cold. Scale dynamically based on write velocity."
- Rollup interval: "A 5-second rollup balances read freshness against overhead; choose the interval from freshness and storage-load requirements."
- Read path formula: "Displayed count = snapshot + SUM(active shards). Maximum staleness equals the rollup interval."
- Decrements: "Deletes and spam removal decrement a random shard. Shards can go negative. The math works cleanly."
- Reconciliation: "Hourly job compares counter value against SELECT COUNT(*) from comments. Corrects drift from failed increments or race conditions."
- Read-your-own-writes: "Per-user session adjustment with TTL of 2x the rollup interval. Added to the global count for that user only."
- Storage choice: "Bigtable or Spanner for durable counters. Redis as read-through cache. Never Redis as the sole counter store."
- CDN caching: "2 to 5 second TTL on count endpoints. Acceptable staleness avoids thundering herd on the counter store."
Test Your Understanding
Quick Recap
- Single-row counters at high write throughput create lock contention that cascades into outages. This is the fundamental problem.
- Sharded counters spread writes across N independent rows, dividing contention by N. Use 64 shards for hot videos, 4 for cold.
- A periodic rollup job (every 5 seconds) consolidates shard values into a snapshot, keeping reads fast and shard values small.
- The displayed count is
snapshot + SUM(active shards), at most one rollup interval stale. - Anti-spam reconciliation handles retroactive comment removal by decrementing shards (which can go negative) and running hourly drift-correction against the source of truth.
- Read-your-own-writes uses a per-session adjustment with TTL to give the commenter immediate feedback without breaking the eventually consistent model.
- CDN caching with a 2 to 5 second TTL protects the counter store from thundering-herd reads while keeping displayed counts near real-time.
- Counter drift (from failed increments, race conditions, or crashed processes) is corrected by the reconciliation job, which compares the counter against actual row counts.
Related Concepts
- Distributed counters in globally distributed databases explores the same sharded counter pattern and the coordination techniques used to roll up shards across regions.
- CRDTs (Conflict-free Replicated Data Types) provide a theoretical foundation for counters that merge without coordination, which is the academic basis for grow-only and positive-negative counter patterns.
- Write-behind caching applies a similar "buffer writes, flush periodically" strategy to general-purpose cache updates, not just counters.
- Event sourcing takes the counter problem further by storing every increment as an event and deriving the count from the event log, giving full auditability at the cost of storage.
- Hot key mitigation addresses the broader problem of traffic concentration on a single key, which is exactly what sharded counters solve for the counter-specific case.