Answer A: Anthropic Claude Sonnet 5
Real-Time Notification System Design for a Social Media App
1. Requirements Recap & Back-of-the-Envelope Estimation
Traffic estimation:
- 10M DAU × 20 notifications/day = 200M notifications/day
- Average rate = 200M / 86,400s ≈ 2,315 notifications/sec
- Peak rate (5x) ≈ 11,500 notifications/sec
- At 100M DAU (future): average ~23,000/s, peak ~115,000/s
Storage estimation:
- Each notification record ~500 bytes (metadata + payload)
- Daily storage: 200M × 500B = 100GB/day → ~36TB/year (before compression/archival)
- History requirement (last 100 per user) is a "hot" read-heavy access pattern, favo...
Show Full Answer ▼
Real-Time Notification System Design for a Social Media App
1. Requirements Recap & Back-of-the-Envelope Estimation
Traffic estimation:
- 10M DAU × 20 notifications/day = 200M notifications/day
- Average rate = 200M / 86,400s ≈ 2,315 notifications/sec
- Peak rate (5x) ≈ 11,500 notifications/sec
- At 100M DAU (future): average ~23,000/s, peak ~115,000/s
Storage estimation:
- Each notification record ~500 bytes (metadata + payload)
- Daily storage: 200M × 500B = 100GB/day → ~36TB/year (before compression/archival)
- History requirement (last 100 per user) is a "hot" read-heavy access pattern, favoring a design where recent notifications are cheap to fetch (e.g., capped list per user) while full history goes to cold storage.
This confirms we're dealing with a write-heavy, fan-out-heavy, latency-sensitive system that must decouple ingestion from delivery.
2. High-Level Architecture
[Event Producers] → [Event Bus / Kafka] → [Notification Service (Consumers)]
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
[Preference Service] [Rendering/Templating] [Rate Limiter/Dedup]
│ │ │
└───────────────┬────────────┴───────────────────────────┘
▼
[Delivery Dispatcher / Router]
┌─────────────┬─────────────┬─────────────┐
▼ ▼ ▼ ▼
[Push Service] [WebSocket/ [Email Service] [In-App Store
(FCM/APNs)] SSE Gateway] (SES/SendGrid) Write - DynamoDB/Cassandra]
│ │
[Mobile Devices][Connected Clients]
Flow:
- A source system (e.g., Like Service, Comment Service, Follow Service, Messaging Service) emits an event (e.g.,
user_liked_post) to a durable message bus (Kafka). - The Notification Orchestrator consumes these events, checks user preferences, applies dedup/rate-limiting/batching logic (e.g., "John and 5 others liked your post"), and generates a notification object.
- The orchestrator persists the notification (for history) and pushes it to a Delivery Dispatcher, which fans out to the appropriate channels based on user preference and device state (online vs offline).
- Delivery workers handle actual transmission: push notification providers (FCM for Android, APNs for iOS), WebSocket/SSE for in-app real-time badge updates, and email via a transactional email provider.
3. Key Components
3.1 Event Ingestion Layer — Apache Kafka
- All source services publish events to Kafka topics (partitioned by
user_idto preserve per-user ordering). - Kafka provides durability (replication factor 3), high throughput, and natural buffering during traffic spikes — critical since peak traffic is 5x average.
- Topics:
notification.likes,notification.comments,notification.followers,notification.messages(or a single topic with event type field, depending on schema evolution needs).
Why Kafka over SQS/RabbitMQ? Kafka handles very high throughput with low per-message overhead and supports replay (useful for reprocessing failed batches or backfilling). SQS is simpler operationally but harder to scale to >100k msg/s cost-effectively and doesn't support consumer-group replay semantics as cleanly.
3.2 Notification Orchestrator Service
- Stateless consumer group reading from Kafka.
- Responsibilities:
- Preference check: Query a fast key-value store (Redis or DynamoDB) for user notification settings before processing further. This avoids wasted work generating notifications a user has opted out of.
- Deduplication/Batching: Use a short-lived aggregation window (e.g., Redis sorted sets with TTL) to batch similar events (e.g., multiple likes on the same post within 60 seconds become one notification).
- Fan-out for followers: For events like "new post from someone you follow," this could require fanning out to millions of followers (celebrity problem). Use a hybrid fan-out model:
- Fan-out-on-write for regular users (push to each follower's notification feed immediately).
- Fan-out-on-read for celebrities/high-follower accounts (compute at read time to avoid a write storm).
- Horizontally scalable — scale consumer instances based on Kafka partition count and lag.
3.3 Notification Store (Persistence Layer)
- Primary store: A wide-column NoSQL database like Apache Cassandra or DynamoDB, partitioned by
user_id, clustered/sorted bytimestamp(descending).- This model is ideal because the dominant access pattern is "get last 100 notifications for user X," which is a simple range query on a partition — no joins needed.
- Cassandra offers tunable consistency and horizontal scalability well past 100M users; DynamoDB offers a fully managed alternative with less operational overhead (trade-off: higher cost at very large scale, and hot-partition risk for extremely active users unless partition keys are salted).
- TTL/Archival: Keep only recent notifications (e.g., 30-90 days) in the hot store; older data archived to cheaper storage (S3 + Glacier) for compliance/audit, with the "last 100" cap enforced at write time (a capped list per user, or trimmed via periodic compaction).
3.4 Delivery Dispatcher
- Reads the finalized notification object and determines channels to use based on:
- User's channel preferences (push/email/both/none)
- User's online status (tracked via a presence service backed by Redis, updated by WebSocket/heartbeat)
- Routes to:
- Push Notification Service: Integrates with FCM (Android) and APNs (iOS). Wrap in an internal abstraction layer to normalize retries, payload formats, and device token management (tokens stored in a
user_devicestable, refreshed on app launch). - In-App Real-Time Delivery: For users actively connected via WebSocket/SSE, push directly to a connection gateway (e.g., a fleet of WebSocket servers behind a load balancer, using something like Socket.IO or a managed service like AWS API Gateway WebSockets). Connection-to-server mapping tracked in Redis so any dispatcher node can find which gateway instance holds a user's connection.
- Email Service: For less time-sensitive notifications (e.g., weekly digest) or as fallback for offline users on certain notification types, integrate with a provider like Amazon SES or SendGrid, using a separate lower-priority queue since email SLAs are more relaxed (~seconds to minutes is fine).
- Push Notification Service: Integrates with FCM (Android) and APNs (iOS). Wrap in an internal abstraction layer to normalize retries, payload formats, and device token management (tokens stored in a
3.5 Preference Service
- Simple service backed by a relational DB (Postgres) or DynamoDB, cached aggressively in Redis (preferences change infrequently, read extremely frequently — perfect caching candidate).
- Schema:
user_id, notification_type, channel, enabled.
4. Data Model
Notification Table (Cassandra/DynamoDB)
Partition Key: user_id
Clustering Key: notification_id (time-based UUID, sorted descending)
Attributes:
- type (like, comment, follow, message)
- actor_id (who triggered it)
- actor_ids (array, for batched notifications)
- target_object_id (post_id, comment_id, etc.)
- message_preview
- created_at
- read_status (boolean)
- delivered_channels (array: push, email, in-app)
User Preferences Table
Partition Key: user_id
Attributes: { likes: {push: true, email: false}, comments: {...}, follows: {...}, messages: {...} }
Device Tokens Table
Partition Key: user_id
Clustering Key: device_id
Attributes: platform (ios/android), token, last_active
5. Ensuring Reliability ("No notification lost")
- Durable messaging: Kafka with replication factor ≥3 and
acks=allon producers ensures events aren't lost before processing. - At-least-once processing with idempotency: Consumers may reprocess on failure/restart, so notification IDs are deterministically generated (e.g., hash of source event ID + type) to allow idempotent writes — prevents duplicate notifications on retry.
- Dead-letter queues (DLQ): Failed deliveries (e.g., push provider timeout) go to a DLQ topic with exponential backoff retry (e.g., 3 retries with jitter), then to manual/alerting review if still failing.
- Write notification to store BEFORE attempting delivery: This decouples "notification exists" (durability/history) from "notification delivered" (best-effort real-time). Even if push delivery fails, the user sees it next time they open the app and poll the notification API.
- Outbox pattern in source services: To avoid dual-write problems (DB write + event publish), use the transactional outbox pattern so that when a "like" is recorded in the source service's DB, the event is guaranteed to also be published to Kafka via a change-data-capture (CDC) tool like Debezium.
6. Scalability Strategy (10M → 100M DAU)
- Kafka: Increase partition count (partitioned by user_id hash) — scales linearly with more brokers/consumers.
- Notification Orchestrator & Dispatcher: Stateless, horizontally scalable behind consumer groups; scale via Kubernetes HPA based on Kafka consumer lag.
- Cassandra: Add nodes to the ring; consistent hashing distributes load automatically. Watch for hot partitions from viral content/celebrity accounts — mitigate via bucketing (e.g., splitting a celebrity's fan-out across multiple shard keys).
- WebSocket Gateway: Scale horizontally; use sticky sessions via a connection registry in Redis so dispatchers know which gateway node owns which connection, regardless of how many gateway nodes exist.
- Push/Email providers: These are third-party managed services (FCM, APNs, SES) that scale independently; our responsibility is batching and rate-limiting to stay within provider quotas.
- Caching: Aggressive Redis caching of preferences and presence data reduces DB load as user count grows 10x.
7. Key Trade-offs
| Decision | Trade-off |
|---|---|
| Kafka vs. simpler queue (SQS) | Kafka adds operational complexity (needs dedicated ops expertise, ZooKeeper/KRaft management) but provides far superior throughput and replay capability needed at this scale. |
| Cassandra vs. DynamoDB | Cassandra gives more control and potentially lower cost at very large scale but requires in-house ops; DynamoDB is fully managed (faster to build, less ops burden) but can get expensive at extreme scale and has stricter item-size/throughput-partitioning constraints. |
| Fan-out-on-write vs. fan-out-on-read | Write-time fan-out gives lower read latency (great for the 2-second SLA) but risks a "thundering herd" write storm for celebrities; read-time fan-out avoids that but adds latency and computation at read time. The hybrid approach balances both but adds design/code complexity (two code paths). |
| Storing full history vs. capped 100 notifications hot + archive | Reduces hot storage costs and keeps queries fast, but requires an archival/retrieval path for compliance or "load more" features, adding complexity. |
| At-least-once delivery + idempotency vs. exactly-once | Exactly-once semantics in distributed systems are expensive/complex (needs transactional consumers); at-least-once + idempotent writes achieves the same practical guarantee (no duplicate visible notifications) at much lower operational cost. |
| Real-time push for everything vs. batching/aggregation | Aggregating similar notifications (e.g., "10 people liked your photo") reduces notification fatigue and delivery volume, improving both UX and cost, at the expense of slightly more complex orchestration logic and a small buffering delay (still well within the 2-second SLA if window is short, e.g., 5-10s max hold before flush). |
8. Monitoring & Alerting
- Track Kafka consumer lag, delivery success/failure rate per channel, P50/P95/P99 end-to-end latency (event ingestion → device delivery), and DLQ depth.
- SLA dashboards alert on-call if P95 latency exceeds 2s or DLQ growth indicates a systemic delivery failure (e.g., FCM outage), triggering automatic failover logic (e.g., queue holding + retry with backoff rather than dropping).
Summary
The design decouples notification generation (event-driven, Kafka-based, idempotent) from delivery (multi-channel dispatcher with per-channel retry/backoff), backed by a wide-column store optimized for the "recent history per user" access pattern. This separation is what allows the system to meet the 2-second latency SLA while guaranteeing no data loss, and it scales horizontally at every tier (ingestion, processing, storage, delivery) to support 10x future growth with manageable operational and cost overhead.
Result
Winning Votes
0 / 3
Average Score
Total Score
Overall Comments
Answer A is a strong system design proposal with accurate capacity estimates, a logical event-driven architecture, appropriate use of Kafka, Cassandra/DynamoDB, Redis, WebSockets, APNs/FCM, and email providers, and good coverage of persistence, preferences, retries, DLQs, idempotency, monitoring, and trade-offs. Its main weaknesses are that some areas are less precise than they could be, such as the exact latency boundary, HA/DR strategy, API/read-path details, overload prioritization, and nuanced delivery semantics around external providers.
View Score Details ▼
Architecture Quality
Weight 30%Answer A presents a coherent architecture with event producers, Kafka, notification orchestration, preference checks, storage, dispatchers, WebSocket delivery, push providers, and email workers. The flow is logical and complete, though some read-path/API and channel-command separation details are less explicit.
Completeness
Weight 20%Answer A covers the main requirements: notification types, near-real-time delivery, push/email/in-app channels, last-100 history, preferences, scalability, reliability, cost, monitoring, and data models. It is somewhat lighter on API details, security/privacy, disaster recovery, and exact operational behavior during overload or provider outages.
Trade-off Reasoning
Weight 20%Answer A includes a useful trade-off table covering Kafka vs. SQS, Cassandra vs. DynamoDB, fan-out-on-write vs. fan-out-on-read, capped hot storage vs. archive, at-least-once vs. exactly-once, and batching vs. real-time delivery. The reasoning is solid, though some trade-offs are summarized rather than deeply tied to operational consequences.
Scalability & Reliability
Weight 20%Answer A gives strong scalability and reliability mechanisms: Kafka replication and replay, idempotent writes, DLQs, retries, outbox pattern, horizontal scaling, Cassandra/DynamoDB partitioning, Redis caching, and WebSocket scaling. It is less detailed on multi-region recovery, overload prioritization, consumer offset discipline, provider delivery limits, and exact SLO semantics.
Clarity
Weight 10%Answer A is clearly structured, easy to follow, and uses diagrams, bullets, schemas, and a concise trade-off table effectively. It communicates the design efficiently with minimal ambiguity.
Total Score
Overall Comments
Answer A is a polished, well-structured design proposal that would read well in an interview setting. It nails the capacity math, provides a readable architecture diagram, concrete data models, a clean trade-off table, and covers the outbox pattern, idempotency, DLQs, and horizontal scaling at every tier. Its weaknesses are depth and breadth in a few areas: no API/read-path design, no security or privacy discussion, no HA topology or disaster recovery, no priority isolation between notification classes, and it uncritically accepts the 2-second end-to-end SLA without noting that third-party providers make it unenforceable. The fan-out-on-read suggestion is also a slightly awkward fit for a notification inbox.
View Score Details ▼
Architecture Quality
Weight 30%Presents a clear layered architecture with an ASCII diagram, event bus (Kafka), orchestrator, preference service, dispatcher, WebSocket gateway, push/email workers, and a wide-column store. Flow is easy to follow and components are well delineated. Slight weaknesses: the fan-out-on-read discussion is somewhat misapplied to notifications (it is a feed concept), and the API/read path plus API gateway layer are barely addressed despite being called out in the judging policy.
Completeness
Weight 20%Covers estimation, all four notification types, push/email/in-app channels, history via capped list plus archival, preferences with schema, device tokens, reliability, scalability, and monitoring. Missing or thin: API design/read path, security and privacy, disaster recovery and multi-region strategy, unread counts, and explicit HA topology (AZ spread).
Trade-off Reasoning
Weight 20%A dedicated trade-off table covers Kafka vs SQS, Cassandra vs DynamoDB, fan-out on write vs read, hot storage vs archive, at-least-once vs exactly-once, and batching vs immediacy. Each entry names both benefit and cost, which is clear and readable. However, the trade-offs are mostly conventional and stated briefly, with less depth on consistency boundaries, ordering guarantees, or SLO definition nuances.
Scalability & Reliability
Weight 20%Solid: Kafka partitioning and replication, acks=all, at-least-once with deterministic idempotent IDs, DLQ with exponential backoff and jitter, write-before-deliver, transactional outbox with Debezium, HPA on consumer lag, Cassandra ring expansion, hot-partition salting, and Redis connection registry. Lacks explicit multi-AZ/multi-region HA, DR/failover procedures, backpressure or priority isolation between notification classes, and offset-commit semantics.
Clarity
Weight 10%Excellent readability: numbered sections, an ASCII architecture diagram, code blocks for the data model, a trade-off table, and a concise closing summary. Easy to skim and quick to grasp the design at a glance.
Total Score
Overall Comments
Answer A provides a very strong and well-structured system design. Its key strengths are its clarity and organization, using a diagram and a table to make complex concepts easy to understand. It covers all the core requirements of the prompt, proposing a logical architecture with appropriate technology choices and sound strategies for scalability and reliability. However, it lacks the depth and breadth of Answer B, particularly in areas like API design, security, and detailed disaster recovery planning.
View Score Details ▼
Architecture Quality
Weight 30%The proposed architecture is logical, complete, and well-suited for the task. It clearly identifies all major components and their interactions, and the inclusion of a diagram greatly aids understanding. The flow from event producers to delivery channels is well-defined.
Completeness
Weight 20%The answer addresses all functional and non-functional requirements specified in the prompt. It covers the main aspects of the design, including estimations, components, data models, and strategies for scaling and reliability.
Trade-off Reasoning
Weight 20%The answer clearly discusses key trade-offs in a dedicated table, which is very effective. It provides sound reasoning for choices like Kafka over SQS and Cassandra over DynamoDB, demonstrating a good understanding of the principles involved.
Scalability & Reliability
Weight 20%The design effectively addresses scalability and reliability. It proposes standard, effective techniques like horizontal scaling for services, partitioning in Kafka and Cassandra, and using DLQs and idempotency for reliability. The strategies are sound and well-explained.
Clarity
Weight 10%The answer is exceptionally clear and well-organized. The use of headings, a flow diagram, and a table for trade-offs makes the complex design easy to follow and digest. The writing is direct and to the point.