Low Level Design: Reactions Service

Schema

Reaction (
  entity_type   VARCHAR(50),
  entity_id     BIGINT,
  user_id       BIGINT,
  reaction_type ENUM('like','love','haha','wow','sad','angry'),
  created_at    TIMESTAMP,
  PRIMARY KEY (entity_type, entity_id, user_id)
)

ReactionCount (
  entity_type   VARCHAR(50),
  entity_id     BIGINT,
  reaction_type VARCHAR(20),
  count         INT DEFAULT 0,
  PRIMARY KEY (entity_type, entity_id, reaction_type)
)

The composite PK on Reaction enforces one reaction per user per entity at the DB level.

One Reaction Per User

Enforced by the primary key. A user can change their reaction type but cannot hold multiple reactions on the same entity simultaneously.

Changing a Reaction

Upsert on the Reaction table:

INSERT INTO reaction (entity_type, entity_id, user_id, reaction_type, created_at)
VALUES (?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE reaction_type=VALUES(reaction_type);

Decrement old reaction_type count, increment new reaction_type count in ReactionCount — done atomically in a transaction.

Read Path

GET /reactions/{entity_type}/{entity_id}

Response:
{
  "counts": {"like": 142, "love": 38, "haha": 7},
  "viewer_reaction": "like"   // null if not reacted
}

Counts read from ReactionCount (fast). Viewer's own reaction fetched by PK lookup on Reaction.

Write Path

POST /reactions
{"entity_type": "post", "entity_id": 42, "reaction_type": "love"}

// Remove reaction:
DELETE /reactions?entity_type=post&entity_id=42

At Scale

  • Shard ReactionCount by entity_id to distribute write load.
  • Redis counters: use HINCRBY reactions:{entity_type}:{entity_id} {reaction_type} 1 for hot entities. Async job syncs Redis counts to DB periodically.
  • Read cache: cache count response in Redis with short TTL (e.g., 5s) for viral content.

Batch Fetch

POST /reactions/batch
{"entity_type": "post", "entity_ids": [1, 2, 3, 42]}

Response:
{
  "1":  {"counts": {...}, "viewer_reaction": null},
  "42": {"counts": {...}, "viewer_reaction": "like"}
}

Single query with WHERE entity_id IN (...) on ReactionCount.

Real-Time Broadcast

On reaction write, publish event to Redis channel reactions:{entity_type}:{entity_id}. WebSocket server fan-outs updated counts to all connected viewers of that entity. Clients update counts in place without a full page reload.

PUBLISH reactions:post:42 '{"reaction_type":"love","delta":1,"total_counts":{...}}'

See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Twitter/X Interview Guide 2026: Timeline Algorithms, Real-Time Search, and Content at Scale

Scroll to Top