· software-engineers Editorial · Career · 6 min read
Swe System Design Notification Service
Design a scalable notification service: fan-out, delivery guarantees, rate limiting, and multi-channel routing for interviews.
Why Notification Service Design Is a Top-5 Interview Question in 2026
Ask any staff engineer which system design prompts show up most across FAANG and mid-size tech interviews in 2026, and “design a notification system” ranks alongside URL shorteners and rate limiters. It’s popular because it forces candidates to reason about fan-out at scale, multiple delivery channels (push, email, SMS, in-app), idempotency, and failure handling — all in 45 minutes.
Unlike a pure storage problem, a notification service is a coordination problem: you’re orchestrating third-party providers (APNs, FCM, Twilio, SendGrid) that have their own rate limits, latencies, and failure modes, while guaranteeing your own delivery semantics to end users.
Requirements Gathering: What Interviewers Want to Hear
Before drawing boxes, state functional and non-functional requirements out loud. Functional: support push, email, SMS, in-app notifications; support templated content; support user preferences (opt-out, quiet hours); support scheduled and immediate sends. Non-functional: at-least-once delivery, sub-second p99 latency for immediate triggers, handle 500K notifications/second at peak (think Black Friday flash sale alerts), and graceful degradation when a downstream provider is down.
Candidates who skip this step and jump straight to drawing a queue lose points immediately. The 0-to-1 SWE Interview Playbook (https://www.amazon.com/dp/B0H256Z1MF?tag=sirjohnnymai-20) frames this as the “clarify before you draw” discipline — a full chapter is dedicated to notification and messaging system prompts with example dialogues showing exactly what to ask.
High-Level Architecture
The standard shape: a Notification API (ingestion) accepts requests from internal services, validates and enriches them (user preferences, template resolution), and writes to a Notification Queue partitioned by channel. Downstream, per-channel Worker Pools consume from the queue and call the appropriate third-party provider (FCM for Android push, APNs for iOS, Twilio for SMS, SES/SendGrid for email).
A Preference Service backed by a fast key-value store (Redis or DynamoDB) gates every send — users who opted out or are in quiet hours get filtered before hitting the queue, not after, to avoid wasted provider calls.
A Template Service resolves notification content server-side so mobile clients never ship copy — this lets marketing update push copy without an app release.
Delivery status flows back through a Delivery Tracking table (event-sourced: queued → sent → delivered → failed → retried) so product teams can build funnels and debug missed notifications.
Client Services → Notification API → Preference Filter → Kafka (partitioned by channel)
↓
Push Worker Pool | Email Worker Pool | SMS Worker Pool
↓
APNs/FCM | SES/SendGrid | Twilio
↓
Delivery Tracking Store
Fan-Out Strategies: Push vs. Pull vs. Hybrid
For a single notification triggered by a single event (e.g., “your order shipped”), fan-out is trivial — one message, one recipient. The hard case is broadcast fan-out: a notification to millions of users simultaneously (e.g., “flash sale starts now”).
Push model: write the notification to every recipient’s queue immediately at trigger time. Simple to reason about, but a single trigger can spike write load by millions of QPS instantly — this is the classic celebrity/hot-key problem.
Pull model: write one record for the broadcast event; clients poll or fetch on next app-open. Reduces write amplification but adds latency and complexity for real-time push notifications.
Hybrid: push immediately for users who are online/recently active (a “fan-out on write” for the hot set), pull/batch for everyone else (fan-out on read, computed lazily). Most 2026-era systems at scale use this hybrid, batching the long tail through a rate-limited background worker to avoid overwhelming third-party push providers, which themselves enforce per-app rate limits.
Delivery Guarantees, Idempotency, and Retry Logic
At-least-once delivery is the realistic target — exactly-once across an internal queue and an external provider (Twilio, APNs) is not achievable end-to-end, since the provider’s own ack can be lost. What you control: idempotency keys attached to every notification request so retries on your side never double-send. Store a dedup key (hash of user_id + template_id + trigger_event_id) with a TTL in Redis; check it before enqueueing.
Retry logic should use exponential backoff with jitter, capped retry count (3-5 attempts), and a dead-letter queue for notifications that fail permanently (bad phone number, uninstalled app token) — feed the DLQ into an alerting dashboard rather than silently dropping.
Comparison: Notification Delivery Approaches
| Approach | Write amplification | Real-time latency | Best for | Failure isolation |
|---|---|---|---|---|
| Pure push (fan-out on write) | High at broadcast scale | Lowest (near-instant) | Transactional alerts (order shipped, 2FA code) | Per-channel worker isolation |
| Pure pull (fan-out on read) | Low, single write | Higher (depends on poll/next-open) | Non-urgent digests, in-app inbox | Naturally isolated, client-driven |
| Hybrid (push hot set, pull long tail) | Moderate, tunable | Low for active users, moderate for others | Broadcast promos, flash sales | Requires separate hot/cold pipelines |
| Direct synchronous call (no queue) | N/A, but no buffering | Lowest, but fragile | Never recommended at scale | None — provider outage cascades to caller |
Rate Limiting Against Third-Party Providers
Every provider imposes hard caps (FCM: ~4,000 messages/second per project by default; Twilio: account-tier-dependent). Your worker pools need a token-bucket or leaky-bucket limiter per provider credential, with backpressure applied upstream to the queue consumer group rather than dropping messages. Partition Kafka topics by channel so a Twilio slowdown doesn’t stall push notification consumers.
FAQ
Q: How do you prevent duplicate notifications when a worker crashes mid-send? A: Idempotency keys checked in Redis before the provider call, combined with at-least-once queue semantics (ack only after confirmed provider response) and a short-TTL dedup cache. Accept that true exactly-once is unattainable across the provider boundary — design for safe retries instead.
Q: Should notification preferences be checked before or after enqueueing? A: Before. Filtering at ingestion (in the Notification API layer) avoids wasting queue capacity and provider quota on notifications that will be discarded, and it’s the answer interviewers expect — filtering post-queue is a common candidate mistake that signals not thinking about cost at scale.
Q: What’s the biggest signal a candidate misses in this problem? A: Failing to distinguish transactional notifications (must be low-latency, e.g. security codes) from broadcast/marketing notifications (can tolerate delay, benefit from batching). Treating both the same way is the most common mid-level mistake, and interviewers specifically probe for this distinction.
The notification service prompt rewards candidates who think in terms of failure isolation and provider-specific constraints rather than a generic “queue + workers” answer. The 0-to-1 SWE Interview Playbook (https://www.amazon.com/dp/B0H256Z1MF?tag=sirjohnnymai-20) includes a full worked transcript of this exact question with follow-up probes an interviewer is likely to ask.