SD System Design In Depth
0%
lesson system-designsocialreactions

Reaction modeling

Module 4.4: Real-Time Messaging & Feed Architectures Updated Sep 18, 2026
📄 Cheat Sheet
Design the Facebook Like Button | Systems Design Questions 3.0 With Ex-Google SWE
Jordan has no life Design the Facebook Like Button | Systems Design Questions 3.0 With Ex-Google SWE Watch on YouTube ↗

Dedicated to exactly this problem: per-user reaction state, idempotency, hot counters and asynchronous aggregation.

0:00 / --:--

Designing Like and Reaction Systems

Likes and reactions (Love, Haha, Wow) generate the highest volume of write traffic on a social network. Modeling this data efficiently is vital for both fast ingestion and fast display.

Denormalization is Key

When rendering a feed, you need to know two things: the total reaction count, and whether the viewing user has reacted. A normalized SQL approach (checking a Reactions table for every post) is too slow.

Instead, the Posts table itself stores denormalized counters: like_count, love_count, etc. When a reaction occurs, these counters are incremented. The user's specific reaction is stored as a durable (post, user) record, which is the source of truth from which counters are derived; a fast key-value cache of it lets the UI highlight the correct button.

Handling High Write Throughput

For viral posts, incrementing a counter in a database causes severe lock contention (Hot Key Problem). If a million people like a post in a minute, updating that single row directly degrades into lock contention, timeouts and failed writes.

To solve this, systems use asynchronous buffering. Reactions are dropped into a Kafka queue. A background worker aggregates the counts (e.g., "+500 likes in the last second") and performs a single bulk update to the database. Alternatively, Redis can handle high-throughput atomic increments (INCR) and sync to the persistent database periodically. Either way, increments must be idempotent and deduplicated (count only state transitions of the (post, user) record), otherwise retries and replays double-count.

Worked example: a viral post

One million likes arrive in 60 seconds: about 16,700 per second on a single post. A single relational row under contention serializes on its row lock and typically sustains only hundreds to low thousands of updates per second, so updating posts.like_count directly cannot keep up.

  1. Source of truth: reactions(post_id, user_id, type, updated_at) with primary key (post_id, user_id). An upsert is naturally idempotent: pressing like twice leaves one row.
  2. Change events: the upsert emits a delta only when the state actually changes: none to like gives like +1; like to love gives like -1 and love +1; love to none gives love -1. Emit via an outbox table or change data capture so the event is not lost if the process crashes after commit.
  3. Aggregation: a stream job sums deltas per post over 1 second windows and applies one write per post per second, turning 16,700 writes per second into 1.
  4. Alternative: sharded counters, for example 64 sub-counter rows per hot post (about 260 writes per second each), summed on read or cached.

Design options

ApproachWrite throughputCount freshnessCorrectness risk
Update counter column directlyLow on hot rowsImmediateLock contention, timeouts
Redis INCR, periodic flushVery highSecondsLost increments on failover unless reconciled
Sharded countersHigh, scales with shard countImmediate but read sums N rowsMore complex reads
Stream aggregation of deltasVery highWindow length (1 to 10 s)Needs dedup and idempotent apply
flowchart LR U["User taps reaction"] --> DB["Upsert reactions row"] DB --> OB["Outbox or CDC delta"] OB --> K["Kafka topic keyed by post"] K --> AGG["Windowed aggregator"] AGG --> CNT["Counter store per post and type"] CNT --> FEED["Feed rendering reads counts"]

Reconciliation and real systems

  • Run a periodic job that recounts reactions rows for recently active posts and corrects counter drift.
  • YouTube famously froze public view counts near 300 while it validated views (until it retired the "301+" freeze in 2015), a real example of choosing correctness over live freshness.
  • LinkedIn's live video reactions travel through an in-memory publish/subscribe layer to viewers, while the durable counts are updated separately.
Check yourself Question 1 of 3
Source of truth for likes recall
From: Designing Like and Reaction Systems

In the lesson's reaction design, what is the durable source of truth from which like and love counts are derived?

Key Takeaways

Reaction counts must be denormalized directly onto the post or content object.
High-velocity reactions on viral posts cause database lock contention.
Use stream aggregation (buffering in Kafka) or Redis to batch database updates.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue