# Low Level Design: Reactions Service

Source: https://www.techinterview.org/post/3233470865/lld-reactions-service/
Updated: 2026-04-17 · techinterview.org

## 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](https://www.techinterview.org/post/3233460272/meta-interview-guide-2026-facebook-instagram-whatsapp-engineering/)

See also: [Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence](https://www.techinterview.org/post/3233460274/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](https://www.techinterview.org/post/3233460305/twitter-x-interview-guide-2026-timeline-algorithms-real-time-search-and-content-at-scale/)
