Orivel Orivel
Open menu

Design a Real-Time Notification System for a Social Media App

Compare model answers for this System Design benchmark and review scores, judging comments, and related examples.

Login or register to use likes and favorites. Register

X f L

Contents

Task Overview

Benchmark Genres

System Design

Task Creator Model

Answering Models

Judge Models

Task Prompt

You are a senior software engineer tasked with designing a real-time notification system for a rapidly growing social media application. The system needs to be scalable, reliable, and deliver notifications with low latency. Provide a detailed system design proposal.

Task Context

The social media application has the following characteristics and requirements:

Scale:

  • 10 million Daily Active Users (DAU).
  • Each user receives an average of 20 notifications per day.
  • Peak traffic can be up to 5 times the average traffic.

Functional Requirements:

  • Notification Types: Likes, comments, new followers, direct messages.
  • Delivery: Notifications must be delivered in near real-time (under 2 seconds latency).
  • Channels: Support for both in-app push notifications (...
Show more

The social media application has the following characteristics and requirements:

Scale:

  • 10 million Daily Active Users (DAU).
  • Each user receives an average of 20 notifications per day.
  • Peak traffic can be up to 5 times the average traffic.

Functional Requirements:

  • Notification Types: Likes, comments, new followers, direct messages.
  • Delivery: Notifications must be delivered in near real-time (under 2 seconds latency).
  • Channels: Support for both in-app push notifications (to mobile devices) and email notifications.
  • History: Users must be able to view their last 100 notifications.
  • Preferences: Users can enable/disable specific types of notifications.

Non-Functional Requirements:

  • High Availability: The system must be highly available with minimal downtime.
  • Reliability: No notifications should be lost.
  • Scalability: The architecture must be able to scale to support 100 million DAU in the future.
  • Cost-Effectiveness: The design should be mindful of operational costs.

Your proposal should cover the high-level architecture, key components, technology choices, data model, and strategies for ensuring scalability and reliability. Be sure to explain the trade-offs you considered in your design.

Judging Policy

A high-quality answer will present a coherent and well-reasoned system design. Evaluate the response based on the following criteria:

  1. Architecture: Is the proposed high-level architecture logical and complete? Does it clearly identify major components like API gateways, notification services, message queues, databases, and third-party push services?
  2. Technology Choices: Are the technology choices (e.g., Kafka vs. RabbitMQ, NoSQL vs. SQL, specific database choices like Cassandra or Redis) appropriate...
Show more

A high-quality answer will present a coherent and well-reasoned system design. Evaluate the response based on the following criteria:

  1. Architecture: Is the proposed high-level architecture logical and complete? Does it clearly identify major components like API gateways, notification services, message queues, databases, and third-party push services?
  2. Technology Choices: Are the technology choices (e.g., Kafka vs. RabbitMQ, NoSQL vs. SQL, specific database choices like Cassandra or Redis) appropriate for the requirements and are the justifications for these choices sound?
  3. Scalability and Reliability: Does the design effectively address how the system will scale to handle peak loads and future growth? Are mechanisms for reliability and fault tolerance, such as retries, dead-letter queues, and database replication, clearly explained?
  4. Trade-offs: Does the answer demonstrate a deep understanding of system design principles by explicitly discussing the trade-offs made (e.g., consistency vs. availability, cost vs. performance, push vs. pull mechanisms)?
  5. Completeness: Does the design address all the specified functional and non-functional requirements, including notification history, user preferences, and multiple delivery channels?

Answers

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:

  1. 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).
  2. 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.
  3. 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).
  4. 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_id to 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 by timestamp (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_devices table, 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).

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")

  1. Durable messaging: Kafka with replication factor ≥3 and acks=all on producers ensures events aren't lost before processing.
  2. 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.
  3. 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.
  4. 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.
  5. 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

#2

Winning Votes

0 / 3

Average Score

83
Judge Models OpenAI GPT-5.5

Total Score

85

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%
85

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%
83

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%
84

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%
85

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%
88

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

80

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%
82

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%
76

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%
80

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%
80

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%
85

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

85

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%
85

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%
80

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%
85

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%
85

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%
90

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.

OVERVIEW

The system should treat every notification as a durable, recipient-specific record and deliver it asynchronously through one or more channels. The durable in-app inbox is the source of truth; mobile push and email are delivery attempts derived from that record. This separation provides low latency without making user-facing social actions wait for external providers such as APNs, FCM, or an email service.

CAPACITY ESTIMATES

At 10 million DAU and 20 notifications per user per day, the system creates about 200 million notifications per day. This is approximately 2,315 notifications per second on average...

Show Full Answer

OVERVIEW

The system should treat every notification as a durable, recipient-specific record and deliver it asynchronously through one or more channels. The durable in-app inbox is the source of truth; mobile push and email are delivery attempts derived from that record. This separation provides low latency without making user-facing social actions wait for external providers such as APNs, FCM, or an email service.

CAPACITY ESTIMATES

At 10 million DAU and 20 notifications per user per day, the system creates about 200 million notifications per day. This is approximately 2,315 notifications per second on average and 11,600 per second at the stated 5x peak. At 100 million DAU, the corresponding rates are about 23,000 per second average and 116,000 per second peak.

These are recipient-notification rates, not merely source-event rates. A celebrity post can produce a hot-key or fan-out spike, so the ingestion and queueing layers should be provisioned above the calculated peak, initially for roughly 25,000 notifications per second and eventually for at least 200,000 per second. Payloads should remain small, with media and full post content referenced by ID rather than embedded.

HIGH-LEVEL ARCHITECTURE

Social services such as Like, Comment, Follow, and Direct Message write their own state and a notification event into a transactional outbox in the same database transaction. Outbox publishers continuously transfer these events to a durable event log such as Apache Kafka. This avoids the dual-write failure in which a social action succeeds but its notification event is lost.

A Notification Processor consumes the source events, validates them, identifies recipients, generates a deterministic notification ID, loads notification preferences, performs lightweight enrichment, and writes the recipient notification into the inbox store. It then publishes one durable delivery command per enabled channel to channel-specific Kafka topics.

Mobile Push Workers consume push commands and invoke APNs or FCM. Email Workers render templates and invoke a provider such as Amazon SES, SendGrid, or an internal mail transfer service. A WebSocket Gateway can also consume in-app delivery commands and immediately update currently connected clients; disconnected clients still see the durable inbox on reconnect. Delivery results and retries are recorded asynchronously.

The main flow is:

Social service and transactional outbox → Kafka source-event topics → Notification Processor → durable inbox store → channel-command topics → WebSocket, APNs/FCM, and email workers.

The read flow is:

Client → API Gateway → Notification API → inbox store and preference store.

CORE COMPONENTS

The API Gateway handles authentication, rate limiting, request routing, and abuse protection. The Notification API supports fetching recent notifications, cursor-based pagination, unread counts, marking one or more items read, and managing preferences.

Kafka provides durable buffering, traffic smoothing, replay, consumer isolation, and horizontal scalability. Topics are replicated across at least three availability zones. Source topics can be partitioned by recipient user ID after recipient expansion, preserving per-user ordering while distributing users across partitions. High-fan-out expansion can use a separate worker pool so one large event cannot block normal traffic.

The Notification Processor should be stateless and autoscaled using queue lag, processing latency, CPU, and throughput. It performs preference evaluation before issuing channel commands. Preferences can be cached in Redis, but the authoritative value remains in a durable store. Cache invalidation events are emitted whenever preferences change.

The inbox store should be a horizontally scalable key-value or wide-column database such as DynamoDB, Cassandra, or ScyllaDB. DynamoDB offers lower operational burden and managed multi-region options; Cassandra or ScyllaDB can be cheaper at sustained large scale but requires more operational expertise. A relational database is less suitable for the primary inbox because write volume, partition growth, and cross-shard scaling would become expensive.

Redis may cache unread counts and the first page of the inbox, but it must not be the system of record. WebSocket gateways are stateless apart from active connection state. A presence directory in Redis or a similar ephemeral store maps users to gateway instances.

DATA MODEL

A notification record contains notification_id, recipient_user_id, type, actor_user_id, object_type, object_id, creation_time, template_version, compact rendering data, read_time, and optional grouping information. Channel state should contain requested channels and delivery status such as pending, dispatched, failed, or suppressed. Large bodies and mutable social content should not be copied into the record unless an immutable snapshot is required.

A suitable inbox key is partition_key = recipient_user_id plus an optional time bucket, and sort_key = reverse_timestamp plus notification_id. Reverse chronological sorting makes the latest page efficient. At ordinary volume, one partition per user is sufficient; exceptionally active accounts can use monthly buckets. The API queries the newest bucket first and follows an opaque cursor into older buckets.

The requirement only exposes the last 100 notifications. The service can retain somewhat more, such as 30 to 90 days, and apply TTL expiration, while the read API returns at most 100. Asynchronously trimming or expiring records is safer and cheaper than performing a synchronous delete on every insert. If strict physical storage of exactly 100 records is required, a background compactor can remove older entries.

Preferences are keyed by user ID and contain per-type, per-channel settings, for example likes.push, likes.email, comments.push, and comments.email, plus global mute, locale, time zone, and a monotonically increasing version. Device tokens are stored separately by user ID and device ID, with platform, token, last-seen time, and validity status. Tokens should be encrypted and invalidated after permanent APNs or FCM errors.

A deterministic notification ID can be derived from source_event_id, recipient_user_id, notification type, and semantic version. The inbox write is conditional on that ID, making replay and duplicate consumption safe. A delivery command has a similarly deterministic ID based on notification ID and channel.

DELIVERY SEMANTICS AND RELIABILITY

The practical guarantee is durable at-least-once processing with idempotent effects. Exactly-once delivery across databases, Kafka, APNs, FCM, and email is not achievable end to end. Transactional outboxes ensure accepted source actions are eventually published. Kafka replication and acknowledged writes prevent queue loss. Conditional inbox writes suppress duplicate records. Channel workers store or otherwise deduplicate delivery attempt IDs, reducing duplicate sends during retries.

A notification is considered accepted only after the source transaction containing its outbox row commits. It is considered durably created after the inbox write succeeds. Kafka offsets are committed only after the corresponding durable write or provider handoff has completed. Transient failures use exponential backoff with jitter. After a bounded number of attempts, commands move to a dead-letter topic with the error, payload reference, and retry history. Operators can replay this topic after remediation.

Mobile providers and email infrastructure cannot guarantee that a device or mailbox presents a notification within two seconds. Therefore, the service should define the latency objective as the time from committed source event to durable inbox visibility and first provider dispatch. A reasonable SLO is 99 percent under two seconds under supported load. APNs and FCM acknowledgments mean provider acceptance, not user display. The durable inbox ensures that provider outages or offline devices do not lose the notification from the user's history.

For currently connected clients, the WebSocket path normally provides the fastest delivery. WebSocket messages carry notification IDs, and clients deduplicate them against fetched inbox records. On reconnect, clients fetch notifications after their last cursor, so dropped socket messages do not create gaps.

SCALABILITY

Kafka topics should begin with enough partitions for expected parallelism and be expanded before reaching partition throughput limits. Recipient ID partitioning spreads ordinary users evenly. Celebrity events should not use actor ID as the partition key because that creates a hot partition. Recipient expansion can divide a large audience into chunks and publish each chunk independently.

Processors, WebSocket gateways, and channel workers are stateless and horizontally scalable. Autoscaling should consider Kafka lag and oldest-message age rather than CPU alone. Separate consumer groups and quotas isolate direct messages from lower-priority likes and email. Under overload, capacity is reserved for direct messages and comments, while email and low-priority likes can queue without being discarded.

Inbox capacity grows linearly with users but remains bounded by TTL and the limited-history requirement. At 100 million DAU and 20 notifications per day, the design must handle roughly two billion daily writes. Time-bucketed user partitions, on-demand or provisioned database capacity, compressed compact records, and no synchronous secondary-index fan-out keep this manageable. Expensive global queries should be served from an analytics pipeline, not the transactional inbox.

HIGH AVAILABILITY AND DISASTER RECOVERY

Every synchronous component runs across at least three availability zones behind health-checked load balancers. Kafka uses replication factor three with strong acknowledgment settings and an appropriate minimum in-sync replica count. The inbox and preference stores use multi-zone replication and point-in-time backups. Deployments use canary or rolling strategies, backward-compatible event schemas, and schema versioning through a registry.

A cost-effective initial design uses one active region with multi-zone redundancy and an asynchronously replicated warm standby region. Recovery procedures promote the standby, restore consumers from replicated offsets or retained events, and replay safely because processing is idempotent. For stricter regional availability, the system can evolve to active-active regional ingestion, with users assigned a home region and globally unique event IDs. Active-active improves recovery time but increases database cost, duplicate handling, ordering complexity, and preference-consistency challenges.

Preference changes should use strongly consistent writes in the user's home region. A notification already durably created before a preference change may still be delivered; this boundary should be documented. For legally required suppression or account deletion, workers should perform an additional authoritative check immediately before external delivery.

LATENCY OPTIMIZATIONS

The critical path contains no synchronous calls to APNs, FCM, or email providers. Event payloads include enough actor and object metadata for basic rendering, avoiding multiple downstream service calls. Missing optional enrichment should produce a generic notification rather than block delivery. Preference records and templates are cached locally or in Redis, and connections to Kafka, databases, and providers are pooled.

Email rendering and dispatch use a separate topic because email is slower and more expensive than in-app delivery. If product requirements allow it, non-urgent likes can be grouped or sent as digest emails, while comments, follows, and direct messages remain immediate. Grouping reduces cost and user fatigue but changes notification semantics, so it must be an explicit product decision.

API DESIGN

GET /v1/notifications?cursor=...&limit=... returns reverse-chronological records, bounded to the latest 100. POST /v1/notifications/read accepts one or more notification IDs or a read-through timestamp. GET and PUT /v1/notification-preferences read and update versioned settings. POST and DELETE device-token endpoints register and revoke devices. All mutation endpoints support idempotency keys.

Unread counts can be maintained as an eventually consistent counter updated from inbox and read events. Since retries can corrupt naive increments, counter updates must be idempotent or periodically reconciled from authoritative records. If exact unread counts are required, the service can query a user-scoped unread index at greater read and storage cost.

OBSERVABILITY AND OPERATIONS

Metrics include event acceptance rate, end-to-end latency by notification type and channel, Kafka lag, oldest queued command, inbox write errors, provider response codes, retry counts, dead-letter volume, WebSocket connection count, preference-cache hit rate, and duplicate suppression rate. Distributed tracing carries source_event_id and notification_id through every stage. Structured logs exclude message bodies and sensitive tokens.

Alerts should be based on SLO burn rates and oldest-message age. Synthetic users continuously generate events and verify inbox, WebSocket, push-provider, and email-provider paths. Dead-letter replay, regional failover, provider outage, Kafka partition loss, and database throttling should be exercised regularly through runbooks and fault-injection tests.

SECURITY AND PRIVACY

Services authenticate with short-lived identities and receive least-privilege access. Data is encrypted in transit and at rest, and device tokens and email addresses receive additional protection. Notification payloads shown on a locked screen should avoid sensitive direct-message content unless the user explicitly enables previews. APIs enforce that users can only access their own inbox and preferences. Retention, account deletion, audit logging, and regional data-residency requirements must be built into the storage lifecycle.

COST AND TRADE-OFFS

Kafka is preferable to synchronous service-to-service calls because it absorbs bursts and enables replay, although it introduces operational complexity and eventual consistency. A managed Kafka service reduces operational risk at higher direct cost. DynamoDB is operationally simple and elastic, while Cassandra or ScyllaDB may lower steady-state storage cost but requires a capable operations team.

Writing the inbox before issuing channel commands ensures history is authoritative and no channel command refers to a missing record, at the cost of a small additional write latency. At-least-once delivery is chosen over an alleged exactly-once design because it remains correct through crashes and external provider retries when paired with deterministic IDs and idempotency.

Recipient-key partitioning preserves per-user order but does not provide global order, which is unnecessary. Strict ordering across every notification would reduce throughput and availability. Similarly, preference caches improve latency but may briefly serve stale values; versioned invalidation and a final check for sensitive suppression balance performance and correctness.

The recommended initial deployment is a managed Kafka cluster, managed DynamoDB-style inbox and preference storage, Redis for ephemeral cache and presence, stateless containerized services on Kubernetes or a managed container platform, APNs/FCM for mobile push, and SES or a comparable email provider. This architecture comfortably supports the current peak, scales horizontally toward 100 million DAU, retains a durable user-visible history, and isolates external delivery failures from the core social application.

Result

#1 | Winner

Winning Votes

3 / 3

Average Score

90
Judge Models OpenAI GPT-5.5

Total Score

93

Overall Comments

Answer B is an exceptionally complete and well-reasoned design. It clearly separates durable inbox creation from external delivery attempts, includes transactional outbox handling, recipient-specific queueing, idempotent at-least-once processing, channel-specific workers, scalable storage design, API behavior, HA/DR, observability, security, cost considerations, and nuanced trade-off analysis. It is slightly long, but the structure remains clear and the added detail is directly relevant to the system design requirements.

View Score Details

Architecture Quality

Weight 30%
92

Answer B provides a very strong architecture with transactional outbox, Kafka source topics, stateless processors, durable inbox store, channel-specific command topics, WebSocket/APNs/FCM/email workers, API gateway, and read APIs. The separation between durable notification creation and delivery attempts is especially well designed.

Completeness

Weight 20%
94

Answer B addresses nearly all specified and implied requirements in depth, including history, preferences, multiple channels, unread counts, device tokens, retries, DLQs, API endpoints, observability, security, retention, HA/DR, and future 100M DAU scaling. It also handles nuanced cases like stale preferences, provider limitations, and reconnect behavior.

Trade-off Reasoning

Weight 20%
93

Answer B demonstrates deep trade-off reasoning throughout the proposal, including managed vs. self-operated infrastructure, inbox-first durability vs. latency, at-least-once vs. exactly-once, active-passive vs. active-active regions, preference cache staleness, recipient partitioning vs. global ordering, and cost vs. performance.

Scalability & Reliability

Weight 20%
95

Answer B is excellent on scalability and reliability. It covers recipient-rate capacity planning, hot fan-out mitigation, partitioning strategy, autoscaling by lag and oldest-message age, idempotent deterministic IDs, conditional writes, offset commit rules, DLQs, replay, multi-AZ deployment, warm standby, active-active evolution, and external provider failure boundaries.

Clarity

Weight 10%
89

Answer B is very clear and organized with well-labeled sections and precise wording. It is longer and denser than Answer A, but the detail is relevant and the main design remains easy to understand.

Total Score

86

Overall Comments

Answer B is a deeper, more operationally mature design. It establishes the durable inbox as the system of record and derives channel deliveries from it, uses transactional outboxes and per-channel command topics with deterministic IDs and conditional writes, and defines precise acceptance/durability boundaries with offset-commit ordering. It goes well beyond A on API design, unread-count consistency, time-bucketed partitioning, HA across AZs, disaster recovery and regional failover, priority isolation under overload, observability with synthetic probes and fault injection, and security/privacy. It also critically reframes the 2-second latency objective as an SLO on inbox visibility and provider dispatch — a genuinely senior insight. Its main weakness is presentation: dense unbroken prose with no diagram, table, or formatted schema, which makes it harder to skim than A.

View Score Details

Architecture Quality

Weight 30%
87

Very strong architecture: transactional outbox with CDC-style publishers, Kafka source topics, stateless Notification Processor that writes the durable inbox as source of truth, then per-channel durable command topics consumed by push/email/WebSocket workers. It explicitly separates write and read paths, includes an API gateway and Notification API with concrete endpoints, presence directory, and separate worker pools for high fan-out. The channel-command topic design and 'inbox as source of truth' framing are more rigorous than A's dispatcher model. Only drawback is the absence of a visual diagram, though the textual flow is explicit.

Completeness

Weight 20%
88

Covers essentially every requirement plus more: capacity estimates for both current and 100M DAU, data model with partition/sort keys and time bucketing, preference versioning and cache invalidation, device token lifecycle and encryption, explicit REST API with cursors and idempotency keys, unread-count consistency, HA across three AZs, DR with warm standby and failover procedure, observability with synthetic canaries, and security/privacy including lock-screen previews and data residency. Very few gaps.

Trade-off Reasoning

Weight 20%
85

Trade-offs are woven throughout and consolidated in a cost section: Kafka vs synchronous calls, managed vs self-hosted, DynamoDB vs Cassandra/ScyllaDB, inbox-write-before-dispatch latency cost, at-least-once with deterministic IDs, per-user vs global ordering, cache staleness vs correctness with an authoritative pre-send check for legal suppression, active-active vs warm standby, and digest grouping as an explicit product decision. Notably it also challenges the 2-second SLA premise, redefining latency as source-commit to durable inbox and first provider dispatch — a genuinely senior-level insight. Format is less scannable than A's table.

Scalability & Reliability

Weight 20%
89

Exceptionally thorough: over-provisioning targets above computed peak, recipient-key partitioning with explicit warning against actor-key hot partitions, chunked recipient expansion, autoscaling on lag and oldest-message age rather than CPU, separate consumer groups and quotas to prioritize DMs over likes under overload, offset commit only after durable write, conditional writes for dedup, DLQ with replay, RF=3 with min-ISR, multi-AZ, warm standby DR with idempotent replay, TTL-bounded storage, and 2B daily writes analysis at 100M DAU. Fault-injection and runbook exercises add operational credibility.

Clarity

Weight 10%
74

Well-organized with clear section headers and precise, dense technical prose, but it is uniformly long paragraphs with no diagram, no table, and no code-formatted schema. Higher cognitive load and harder to skim, though the content itself is unambiguous and logically ordered.

Total Score

92

Overall Comments

Answer B is an exceptional and comprehensive system design proposal that demonstrates a deep level of expertise. It not only meets all the requirements but goes significantly beyond them, discussing critical aspects like API design, security, detailed HA/DR strategies, and observability with professional rigor. The architectural choices are nuanced and well-justified, and the discussion of scalability and reliability is particularly detailed and practical. While its dense prose makes it slightly less accessible than Answer A, its technical depth and completeness are outstanding.

View Score Details

Architecture Quality

Weight 30%
90

The architecture is exceptionally well-conceived, showing a mature understanding of distributed systems. It correctly emphasizes the transactional outbox pattern from the start and proposes a refined flow with separate topics for source events and channel commands. The inclusion of the API gateway and API design considerations makes the architecture more complete.

Completeness

Weight 20%
95

This answer is exceptionally complete. It not only covers all the prompt's requirements but also delves into crucial related areas like API design, security and privacy, detailed high availability and disaster recovery strategies, and observability. This comprehensive approach reflects a holistic and production-ready mindset.

Trade-off Reasoning

Weight 20%
90

The discussion of trade-offs is sophisticated and woven throughout the entire design, with a concise summary at the end. It covers a wide range of decisions, from technology choices to architectural patterns like regional availability models and delivery semantics, showing deep expertise.

Scalability & Reliability

Weight 20%
95

The treatment of scalability and reliability is outstanding. It provides highly specific and practical strategies, such as partitioning by recipient ID to avoid celebrity-induced hot spots, using priority queues, and defining realistic latency SLOs. The detailed section on HA/DR is a significant strength.

Clarity

Weight 10%
85

The answer is well-structured and written with technical precision. However, its prose is quite dense, and the lack of visual aids like diagrams or tables makes it slightly less immediately accessible than Answer A, requiring a more focused read to fully grasp all details.

Comparison Summary

Final rank order is determined by judge-wise rank aggregation (average rank + Borda tie-break). Average score is shown for reference.

Judges: 3

Winning Votes

0 / 3

Average Score

83
View this answer

Winning Votes

3 / 3

Average Score

90
View this answer

Judging Results

Why This Side Won

Answer B is the clear winner due to its superior depth, completeness, and technical nuance. While Answer A provides a very good, standard design, Answer B operates at a higher level of detail and foresight, characteristic of a senior engineer. It addresses critical, real-world considerations that A overlooks, such as API design, security, and detailed disaster recovery plans. B's discussions on scalability (e.g., handling celebrity fan-out) and reliability (e.g., defining realistic SLOs) are more advanced and practical. These strengths in the most heavily weighted criteria—architecture, completeness, and scalability/reliability—make it the better answer.

Why This Side Won

Answer B wins on the weighted result. It edges A on architecture quality (30%) through the inbox-as-source-of-truth model, explicit read/write path separation, and per-channel command topics, and it wins decisively on completeness (20%), scalability and reliability (20%), and trade-off reasoning (20%) by adding API design, HA/DR, priority isolation, offset-commit semantics, security, observability, and a critical reframing of the latency SLO. Answer A is clearly better on clarity (10%) thanks to its diagram, tables, and structure, but that single lightly weighted advantage cannot offset B's superiority across the four heavier criteria.

Judge Models OpenAI GPT-5.5

Why This Side Won

Answer B wins because it provides a more complete and technically precise design across the heavily weighted criteria. Both answers propose sound event-driven architectures, but B more explicitly defines delivery semantics, failure boundaries, replay behavior, high availability and disaster recovery, overload handling, API behavior, security/privacy, and cost trade-offs. Its reliability and scalability discussion is deeper and more operationally grounded, while still addressing all functional requirements.

X f L