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
ReactionCountbyentity_idto distribute write load. - Redis counters: use
HINCRBY reactions:{entity_type}:{entity_id} {reaction_type} 1for 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: Twitter/X Interview Guide 2026: Timeline Algorithms, Real-Time Search, and Content at Scale