· 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

ApproachWrite amplificationReal-time latencyBest forFailure isolation
Pure push (fan-out on write)High at broadcast scaleLowest (near-instant)Transactional alerts (order shipped, 2FA code)Per-channel worker isolation
Pure pull (fan-out on read)Low, single writeHigher (depends on poll/next-open)Non-urgent digests, in-app inboxNaturally isolated, client-driven
Hybrid (push hot set, pull long tail)Moderate, tunableLow for active users, moderate for othersBroadcast promos, flash salesRequires separate hot/cold pipelines
Direct synchronous call (no queue)N/A, but no bufferingLowest, but fragileNever recommended at scaleNone — 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.

Back to Blog

Related Posts

View All Posts »