Loading learning content...
When a user taps the heart icon on TikTok, what appears to be a simple action triggers a cascade of distributed system operations. That single like must:\n\n- Update the visual counter immediately (within 50ms perceived latency)\n- Persist durably so it's never lost\n- Appear to peers who are also viewing the video\n- Feed the recommendation system to improve future personalization\n- Update creator analytics in near-real-time\n- Handle deduplication if the user accidentally double-taps\n- Scale horizontally across billions of daily interactions\n\nTikTok processes over 50 billion engagement actions per day—likes, comments, shares, follows, saves. Each must feel instant while surviving infrastructure failures and maintaining consistency across global data centers.\n\nThis page explores the architecture that makes this possible.
By the end of this page, you will understand: (1) Event-driven architecture for engagement processing, (2) Counter management patterns for high-throughput writes, (3) Optimistic UI patterns for perceived instant response, (4) Comment systems with real-time streaming, (5) Share and re-posting infrastructure, and (6) Engagement signal feedback loops to recommendations.
All engagement actions flow through a unified event-driven architecture designed for high throughput, durability, and real-time processing.
| Event Type | Daily Volume | Latency SLO (P99) | Consistency Model |
|---|---|---|---|
| Like/Unlike | 20 billion | 50ms UI, 1s durable | Eventually consistent (counter), Read-after-write (user's likes) |
| Comment | 2 billion | 200ms visible | Eventually consistent (list), Strong (my comments) |
| Share | 1 billion | 100ms confirmed | Eventually consistent |
| Follow/Unfollow | 500 million | 100ms confirmed | Strong consistency required |
| View Event | 190 billion | Async, <5s | Eventually consistent |
| Save/Bookmark | 1 billion | 100ms confirmed | Read-after-write for user |
| Report | 10 million | 200ms confirmed | Strong consistency |
The like button is deceptively simple—one tap, one state change. But at 20 billion likes per day (~230,000/second average, peaks of 1M+/second), it becomes one of the most challenging systems to design correctly.\n\nThe Core Challenges:\n\n1. Counter Hot Spots: A viral video can receive 100,000 likes/second on a single video ID\n2. Idempotency: Users may double-tap; network retries may duplicate events\n3. Unlike Handling: State must toggle correctly even under race conditions\n4. Display Consistency: User's own like must reflect immediately; total count can lag
user_likes table: (user_id, video_id, timestamp, is_active). Primary key on (user_id, video_id) ensures idempotency.video_stats table: (video_id, like_count, view_count, share_count, comment_count). Managed separately from relationship storage.12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
interface LikeRequest { user_id: string; video_id: string; action: 'like' | 'unlike'; request_id: string; // For idempotency timestamp: number;} interface LikeResponse { success: boolean; new_state: 'liked' | 'not_liked'; like_count: number; // New count (may be approximate)} class LikeService { // Idempotency cache: request_id -> result private idempotencyCache: RedisCluster; // User-video relationship store private userLikesStore: CassandraCluster; // Video counter store private counterStore: RedisCluster; // Event stream private eventStream: KafkaProducer; async processLike(request: LikeRequest): Promise<LikeResponse> { // 1. Idempotency check const cached = await this.idempotencyCache.get(request.request_id); if (cached) { return JSON.parse(cached); // Return cached result } // 2. Check current state const currentState = await this.userLikesStore.get({ user_id: request.user_id, video_id: request.video_id }); // 3. Determine if state change needed const isLiked = currentState?.is_active ?? false; const wantsLiked = request.action === 'like'; if (isLiked === wantsLiked) { // No change needed (idempotent) return { success: true, new_state: isLiked ? 'liked' : 'not_liked', like_count: await this.getApproxCount(request.video_id) }; } // 4. Update relationship await this.userLikesStore.upsert({ user_id: request.user_id, video_id: request.video_id, is_active: wantsLiked, timestamp: request.timestamp }); // 5. Update counter (async, fire-and-forget for speed) const delta = wantsLiked ? 1 : -1; this.counterStore.incrBy(`video:${request.video_id}:likes`, delta); // 6. Emit event for downstream processing await this.eventStream.send({ topic: 'engagement-events', key: request.video_id, // Partition by video for ordering value: { type: 'like', user_id: request.user_id, video_id: request.video_id, action: request.action, timestamp: request.timestamp } }); // 7. Cache result for idempotency const response: LikeResponse = { success: true, new_state: wantsLiked ? 'liked' : 'not_liked', like_count: await this.getApproxCount(request.video_id) }; await this.idempotencyCache.setex( request.request_id, 300, // 5 minute TTL JSON.stringify(response) ); return response; }}A viral video can receive 100K+ likes/second on a single key. Redis INCR on a single key tops out at ~50K ops/sec. Solution: Counter sharding. Split 'video:123:likes' into 100 shards: 'video:123:likes:00' through 'video:123:likes:99'. Writes randomly select a shard; reads sum all shards. Trading accuracy for throughput.
Managing engagement counters (likes, views, shares, comments) is a surprisingly difficult distributed systems problem. The naive approach—increment a database counter—fails at scale due to lock contention and consistency requirements.
| Video Category | Writes/Sec | Counter Strategy | Accuracy |
|---|---|---|---|
| Viral (top 0.01%) | 10K-100K+ | 100 shards + aggressive batching | ±5%, 30s lag |
| Trending (top 1%) | 100-10K | 10 shards + moderate batching | ±1%, 5s lag |
| Normal | 1-100 | Single counter + light batching | Exact, 1s lag |
| Archive (<1 view/hour) | < 1 | No real-time counter; offline aggregation | Exact, 1h lag |
Users don't need exact counts. '1.2M likes' is acceptable even if actual is 1,234,567. Approximate counts serve the display layer; exact counts (when needed for monetization, etc.) come from offline processing with delays up to hours.
Comments are more complex than likes—they involve user-generated text content, threading, moderation, and rich formatting (mentions, hashtags, emojis). TikTok processes approximately 2 billion comments per day.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
interface Comment { comment_id: string; video_id: string; user_id: string; parent_comment_id?: string; // null for top-level comments text: string; timestamp: number; like_count: number; reply_count: number; status: 'pending' | 'approved' | 'hidden' | 'deleted'; entities: CommentEntity[]; // @mentions, #hashtags} interface CommentEntity { type: 'mention' | 'hashtag' | 'url'; start: number; end: number; value: string; // user_id for mentions, tag for hashtags} class CommentService { async createComment(request: CreateCommentRequest): Promise<Comment> { // 1. Rate limit check (max 10 comments/minute per user) await this.rateLimiter.check(request.user_id, 'comment', 10, 60); // 2. Content validation if (request.text.length > 150) { throw new ValidationError('Comment too long'); } // 3. Parse entities (mentions, hashtags) const entities = this.entityParser.parse(request.text); // 4. Spam/toxicity check (fast ML model) const toxicityScore = await this.toxicityModel.score(request.text); const initialStatus = toxicityScore > 0.8 ? 'pending' : 'approved'; // 5. Generate comment ID (time-based for ordering) const commentId = this.generateTimeBasedId(); // 6. Create comment object const comment: Comment = { comment_id: commentId, video_id: request.video_id, user_id: request.user_id, parent_comment_id: request.parent_comment_id, text: request.text, timestamp: Date.now(), like_count: 0, reply_count: 0, status: initialStatus, entities }; // 7. Write to primary store await this.commentStore.insert(comment); // 8. Update parent reply count if this is a reply if (comment.parent_comment_id) { await this.incrementReplyCount(comment.parent_comment_id); } // 9. Update video comment count await this.counterService.increment( `video:${request.video_id}:comments` ); // 10. Emit for downstream processing await this.eventStream.send({ topic: 'comment-events', key: request.video_id, value: { type: 'new_comment', comment } }); // 11. Send notifications (async) this.notificationService.queueCommentNotifications(comment, entities); return comment; } async getComments( videoId: string, sortBy: 'top' | 'recent' = 'top', cursor?: string, limit: number = 20 ): Promise<CommentPage> { // Fetch top-level comments with pagination const comments = await this.commentStore.query({ video_id: videoId, parent_comment_id: null, // Top-level only status: 'approved' }, { sort: sortBy === 'top' ? 'like_count:desc' : 'timestamp:desc', cursor, limit }); // Batch-fetch preview replies for each comment const commentIds = comments.map(c => c.comment_id); const previewReplies = await this.batchFetchPreviewReplies( commentIds, 2 // Show 2 preview replies per comment ); return { comments: comments.map(c => ({ ...c, preview_replies: previewReplies[c.comment_id] || [] })), next_cursor: this.generateCursor(comments) }; }}For live videos or viral content, comments should appear in real-time for all viewers. This requires WebSocket connections with comment broadcasts. At peak, a single viral video might have 100K concurrent viewers receiving real-time comment updates. Pub/sub systems (Redis Pub/Sub, Kafka) fan out new comments to connected clients.
Shares are high-intent engagement signals—users are willing to socially endorse content to their networks. TikTok supports multiple share vectors, each with different system requirements.
| Share Type | Description | System Requirements |
|---|---|---|
| External Share | Share via SMS, WhatsApp, Twitter, etc. | Generate share link, track referral source |
| Copy Link | User copies URL to clipboard | Short URL generation, redirect tracking |
| Direct Message | Share to another TikTok user | DM system integration, notification |
| Duet | Side-by-side video creation | Video composition, original attribution |
| Stitch | Clip + response video | Video clipping, original attribution |
| Repost | Simple repost to own followers | Feed distribution, attribution |
| Download | Save video to device | Watermark addition, quality options |
Duet and Stitch: Remixing Content\n\nDuet and Stitch are TikTok-specific features that transformed content creation.\n\nDuet allows recording alongside an existing video:\n- Client downloads original video segment\n- Records new video simultaneously\n- Composites videos side-by-side (client-side for preview, server-side for upload)\n- Original creator attributed; engagement credited to both\n\nStitch allows clipping the first 5 seconds of an existing video:\n- User selects clip point (max 5s)\n- Records response video\n- Combined into single video with attribution\n- Original creator receives notification and engagement credit\n\nSystem Implications:\n- Must track derivative work relationships (video graph)\n- Engagement flows to original (indirect engagement metrics)\n- Copyright and moderation extend to original content\n- Creators can disable Duet/Stitch per video
Users expect instant feedback when they tap a button. Even a 200ms delay feels sluggish. TikTok achieves <50ms perceived latency through optimistic UI patterns—updating the interface immediately before server confirmation.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
// React hook for optimistic like statefunction useLikeButton(videoId: string) { const [isLiked, setIsLiked] = useState(false); const [likeCount, setLikeCount] = useState(0); const [pending, setPending] = useState(false); const toggleLike = async () => { // 1. Optimistic update (immediate UI change) const newLikedState = !isLiked; setIsLiked(newLikedState); setLikeCount(prev => newLikedState ? prev + 1 : prev - 1); // 2. Haptic feedback (physical confirmation) HapticFeedback.impact('light'); // 3. Queue the request setPending(true); try { const result = await api.toggleLike({ videoId, action: newLikedState ? 'like' : 'unlike', requestId: generateRequestId() // For idempotency }); // 4. Reconcile with server state setIsLiked(result.new_state === 'liked'); setLikeCount(result.like_count); } catch (error) { // 5. Rollback on failure setIsLiked(!newLikedState); setLikeCount(prev => newLikedState ? prev - 1 : prev + 1); // 6. Retry queue for network errors if (isNetworkError(error)) { RetryQueue.enqueue({ type: 'like', videoId, action }); } else { showToast('Could not update like'); } } finally { setPending(false); } }; return { isLiked, likeCount, toggleLike, pending };}Mobile networks are unreliable. Engagement actions are queued locally when offline, synced when connectivity returns. The retry queue uses exponential backoff (1s, 2s, 4s...) with jitter to avoid thundering herd. Actions older than 1 hour are discarded (user expectation expired).
Some scenarios require server-to-client push: live video comments, real-time like count animations, and notification delivery. TikTok uses WebSocket connections for real-time sync.
A viral video might have 1M concurrent viewers. Broadcasting every new comment to 1M clients would require 1M messages per comment. Solution: (1) Aggregate comments client-side (show 'N new comments' instead of instant display), (2) Sample-based fanout (only 10% of clients get real-time, others poll), (3) Edge caching with 2s TTL for comment list.
Engagement signals are not just displayed—they're the lifeblood of the recommendation system. Every like, comment, share, and watch event feeds back into user preference modeling and content quality assessment.
The feedback loop is what makes TikTok feel 'smart'. Unlike batch-trained recommendation systems that take hours/days to adapt, TikTok's real-time feature updates mean your next scroll already reflects your last action. This tight loop is a key differentiator—and a significant engineering investment.
Coming Up Next\n\nEngagement can spike unpredictably when content goes viral. The next page explores Viral Content Handling—how TikTok's infrastructure adapts to handle 1000x traffic spikes on single pieces of content without degradation.
You now understand how TikTok processes 50+ billion daily engagement actions with perceived instant response while maintaining durability and feeding back into the recommendation system. The key insight: optimistic UI, event-driven architecture, and real-time feature updates create the responsive experience users love.