See It Flow
System Design · Interview Perspective

Interview Perspective

How to structure a notification system design interview — the opening sentence, key concepts to demonstrate, common mistakes, and a full question bank of likely follow-ups.

Mangesh MandlikLast updated:
Interview 01

How to Structure Your Answer

Open with the decoupling principle. This immediately signals you understand the core engineering problem.

State early: "I'd decouple notification creation from delivery using durable queues, allowing the system to absorb bursts while workers control delivery according to provider capacity." This one sentence shows you understand why the architecture is the way it is — before you touch any component.

Then walk through requirements: multiple channels, high throughput, priority without starvation, recipient preferences, retries with a deadline, deduplication (reduced, not guaranteed), and delivery tracking that distinguishes acceptance from confirmed delivery. Start simple: synchronous delivery. Show it breaking. Let each failure mode justify each addition.

Interview 02

Key Concepts to Demonstrate

The concepts that separate a strong notification system answer from a generic Producer → Queue → Worker answer.

Queue decoupling

Why the queue exists: ingestion rate is independent of delivery rate. Provider slowness must not block producers. State this explicitly, early.

Durable ingestion

The API's acceptance must be durable before returning success — and the DB/queue dual-write problem (and the transactional outbox pattern that addresses it) is worth raising unprompted.

Fair priority scheduling

Security alerts should get preferential latency — but strict 'always serve high first' can starve lower tiers indefinitely if high-priority volume never fully drains. Weighted polling, reserved capacity, or aging fix this.

Channel isolation

Email failure must not affect push. Per-channel workers and (typically) per-channel queues, each with independent rate limits.

Recipient preferences and eligibility

Preferences, opt-out, quiet hours, and destination validity are resolved before a channel worker acts, not after — and stale cached preferences are a real risk worth naming.

Distributed provider rate limiting

Many workers share one provider quota. A shared or leased limiter is needed — per-worker local limits alone are inaccurate, and more workers cannot bypass a hard provider ceiling.

Retry scheduling with a deadline

Retries are scheduled (delayed queue / retry topic), not implemented as a sleeping worker thread. Retryable vs. permanent errors are classified separately, and every retryable notification needs a deadline so backlog recovery doesn't deliver it stale.

Dead-letter queue

No silent drops. Every unrecoverable failure ends up observable and safely replayable — replay itself must be paced and root-cause-checked, not blind.

Idempotency, not exactly-once

Exactly-once delivery across an external provider cannot be guaranteed. The honest contract is durable at-least-once processing with idempotency keys at the producer/API and provider boundaries, plus reconciliation via callbacks — reducing duplicates aggressively without overclaiming.

Provider failover, carefully

A timeout is not a known rejection — the primary may have already accepted the send. Immediate failover on every timeout risks duplicate delivery; the decision depends on urgency, duplicate tolerance, and whether the outcome was ambiguous or a clean rejection.

Delivery reconciliation via webhooks

Provider callbacks confirm actual delivery, but they can be duplicate, delayed, missing, or out of order — state transitions must be idempotent and monotonic to handle this safely.

Key takeaways
  • Open with the decoupling statement — it's the core insight
  • Raise the DB/queue dual-write problem and the transactional outbox unprompted
  • Name the ambiguous provider-call race explicitly — it's what makes exactly-once impossible
  • Strict priority can starve lower tiers — name the fix (weighted/aging/reserved capacity)
  • Distinguish backpressure (system response) from rate limiting (provider call-rate control)
  • At-least-once + aggressive dedup is the honest contract — not exactly-once
  • SENT/provider-accepted is not the same claim as DELIVERED
Interview 03

Common Mistakes

Mistakes that reveal incomplete understanding of distributed delivery systems.

Immediate retries

Retrying a failed provider call immediately without backoff. This is the direct cause of retry storms.

Backoff without jitter

Exponential backoff ensures retries decrease over time — but synchronized retries still cause spikes. Jitter is not optional.

No DLQ

Treating failed messages as silently lost. This makes delivery quality invisible and unrecoverable.

Strict priority with no fairness mechanism

Assuming 'high always wins' is sufficient. Sustained high-priority volume can starve lower tiers indefinitely — this is a correctness gap, not a tuning detail.

Claiming a pre-send dedup check prevents all duplicates

A dedup check only protects against re-sending after a recorded success. It cannot close the gap where a worker crashes after the provider accepts but before recording that success — retrying there can still duplicate.

Claiming exactly-once delivery

Promising exactly-once delivery across external providers. This cannot be guaranteed. The honest contract is at-least-once with aggressive, not perfect, deduplication.

Failing over on every timeout

A timeout is not a confirmed rejection. Failing over immediately can duplicate a delivery the primary provider had already accepted.

Treating SENT as DELIVERED

Provider acceptance is not confirmed delivery. Not every channel/provider even offers a reliable delivery receipt.

Synchronously fanning out a campaign

Enqueueing millions of recipients inside the original API request defeats the fast-acknowledgment contract and floods downstream queues.

Interview 04

Question Bank: Likely Questions & Follow-Ups

A structured bank of the questions most likely to come up, organized by the area they probe. Each includes a strong-answer direction, likely follow-ups, and a link into the deeper technical pages — this section intentionally doesn't re-teach the full mechanics.

Requirements & Scope

"Design a notification system supporting push, email, and SMS." How do you start?

Strong answer angle

Clarify scope before designing: which notification classes need different latency/SLA guarantees, whether recipients have preferences to honor, whether delivery tracking is required, and how promotional traffic differs from security/transactional traffic in policy terms. Then state the central decoupling principle before touching any component.

Likely follow-ups
  • Which classes need different latency/SLA? — Tests whether the candidate treats all notifications as equally urgent, or recognizes security/OTP traffic has a materially different latency requirement than a promotional campaign.
  • How do promotional and security notifications differ in this design? — Tests whether the candidate connects this distinction to concrete mechanisms — priority tier, opt-out eligibility, retry/expiry policy — rather than treating it as a labeling exercise.

Do users have preferences, and where do they get applied?

Strong answer angle

Yes in a realistic design — category enablement, channel allowance, opt-out state, and quiet hours should all be resolved before a channel worker acts, as part of routing. See Complete Design -> Multi-Channel Routing and Architecture Deep Dive -> Preferences & Channel Eligibility.

Likely follow-ups
  • What if preferences are cached and change while a notification is queued? — Tests awareness of the stale-preference risk — a cached read at enqueue time can be outdated by actual delivery time.

Queue & Delivery

Why put a queue between the API and the provider at all?

Strong answer angle

Because ingestion rate and delivery rate are fundamentally different and shouldn't be coupled — the producer needs to return fast regardless of provider health, and the queue is what makes that possible. See Complete Design -> Introduce the Queue.

Likely follow-ups
  • What if ingestion consistently exceeds delivery capacity, not just in a burst? — Tests whether the candidate recognizes a queue only absorbs temporary mismatch — sustained excess arrival needs a different answer (more provider capacity, shedding, reprioritization), not just a bigger queue.

What queue topology would you use, and does ordering matter?

Strong answer angle

Per-channel (and often per-priority) queues for isolation; most notifications don't need ordering at all, and where per-recipient ordering is genuinely required, partition by recipient ID — accepting the reduced parallelism a hot recipient key creates. See Architecture Deep Dive -> Queue Topology & Ordering.

Likely follow-ups
  • How do channels fail independently in this topology? — Tests whether the candidate can explain this concretely — separate queues, separate consumer groups, separate rate limits per channel — not just assert independence.

Priority & Fairness

How do security alerts bypass promotional traffic?

Strong answer angle

Separate priority tiers/queues, with workers preferentially serving the higher tier. But 'preferentially' matters — see the next question.

Likely follow-ups
  • What if high priority never fully empties? — Tests whether the candidate knows strict priority can starve lower tiers indefinitely under sustained load — this is the key follow-up in this category.
  • How do you prevent starvation? — Tests knowledge of concrete mechanisms: weighted polling, reserved capacity, weighted fair scheduling, or aging. See Architecture Deep Dive -> Priority Scheduling & Starvation and Failure & Scale -> Priority Starvation.

Idempotency / Exactly-Once

A worker crashes right after calling the provider, before recording success — what happens on retry?

Strong answer angle

This is the core ambiguous-outcome race: the provider may have already accepted the request, so the redelivered message might produce a duplicate send. This cannot be fully prevented with only a pre-send dedup check — it can only be reduced, via provider idempotency keys and reconciliation.

Likely follow-ups
  • Can you guarantee exactly-once delivery here? — Tests whether the candidate holds the line: no, not across an external provider, and explaining precisely why (the ambiguous-outcome window) is the strong answer.
  • Where would you use idempotency keys? — Tests whether the candidate distinguishes the three boundaries: producer→API, queue→worker (idempotent processing), and worker→provider (provider idempotency keys where supported).

What if the provider doesn't support idempotency keys at all?

Strong answer angle

Then that specific worker→provider boundary has no protocol-level protection against a duplicate — the mitigation shifts to persisting the provider's request/message ID immediately and reconciling via the provider's status/callback mechanism where one exists, accepting that duplicate risk for this provider is higher than for one with native idempotency support. See Architecture Deep Dive -> Idempotency & Duplicate Suppression.

Likely follow-ups
  • Does this change your provider-failover posture? — Tests whether the candidate connects lack of idempotency support to being more conservative about failover, since duplicate risk there is already higher.

Rate Limiting & Backpressure

Workers can send 50k/s but the provider only allows 10k/s — what do you do?

Strong answer angle

Rate-limit at the provider boundary so workers deliver at 10k/s regardless of their own capacity; the queue absorbs the resulting backlog. Scaling workers further does not help, since the bottleneck is the provider's quota, not worker throughput. See Architecture Deep Dive -> Distributed Provider Rate Limiting.

Likely follow-ups
  • How do distributed workers share that 10k/s quota accurately? — Tests knowledge of centralized shared limiter vs. leased/local quota trade-offs, and why naive per-worker limiting under- or over-shoots the real ceiling.

What happens on a 429 from the provider?

Strong answer angle

Treat it as authoritative feedback that the current send rate is too high — reduce the assumed rate (respecting a Retry-After header if supplied) rather than retrying blindly at the same rate.

Likely follow-ups
  • Why can adding more workers make a 429 situation worse? — Tests the same insight from a different angle: more workers hitting an already-throttled provider just generates more rejected requests and wasted retry traffic.

How would you safely drain a large backlog after the provider recovers?

Strong answer angle

Pace the release to match the provider's sustainable rate rather than releasing everything at once, respect priority ordering, and check each message's expiry/deadline before delivering — some backlogged messages may no longer be worth sending. See Failure & Scale -> Backlog Recovery Storm.

Likely follow-ups
  • What's the risk of releasing the whole backlog at once? — Tests whether the candidate recognizes this can recreate the original outage/throttling condition right as recovery appeared complete.

Retry & DLQ

Which errors should retry, and which shouldn't?

Strong answer angle

Transient/retryable errors (timeouts, 5xx, temporary throttling) should retry with backoff; permanent errors (invalid destination, malformed request, permanently rejected, invalid push token) should not retry at all — they should go straight to a terminal failed state or DLQ.

Likely follow-ups
  • Why is retrying a permanent error wasteful beyond just delay? — Tests whether the candidate connects this to real cost — wasted retry budget, skewed monitoring, and (for a fixed retry ceiling) crowding out legitimate transient retries.

Why backoff and jitter together, and not just one?

Strong answer angle

Backoff alone reduces retry rate over time but doesn't prevent many workers that failed at the same instant from retrying in lockstep. Jitter spreads that synchronized retry mass across a window, which is what actually prevents a thundering-herd spike on a recovering provider.

Likely follow-ups
  • When would you stop retrying? — Tests awareness of both a maximum attempt count and a notification's own deadline/expiry as independent stopping conditions.

How do you replay a DLQ safely?

Strong answer angle

Fix the root cause first, then replay at a controlled/paced rate rather than all at once, retaining attempt/error history, and verifying each message is still relevant (not expired, recipient hasn't since opted out) before replaying. See Complete Design -> Dead-Letter Queue.

Likely follow-ups
  • What if the notification has since expired? — Tests whether the candidate applies the deadline check specifically at replay time, not just at original send time.

Provider Failure & Failover

The SMS provider is down for 30 minutes — what happens?

Strong answer angle

The SMS queue backs up durably; push and email continue unaffected due to channel isolation; backoff+jitter governs retry behavior as the provider recovers, and recovery is paced rather than an instant backlog release. See Failure & Scale -> Provider Outage.

Likely follow-ups
  • Would you fail over to a secondary SMS provider immediately? — Tests the failover-vs-retry trade-off — not a fixed yes/no, but reasoning about urgency, duplicate tolerance, and whether the primary's failures are clean rejections or ambiguous timeouts.

Should you fail over on the very first timeout?

Strong answer angle

Not automatically. A timeout doesn't confirm the primary failed to accept the request — it may have already succeeded. Immediate failover on ambiguous outcomes risks duplicate delivery. See Trade-offs -> Provider Failover vs Retry Primary.

Likely follow-ups
  • How would you protect a fallback provider from also being overwhelmed? — Tests whether the candidate considers that a fallback provider suddenly receiving all failed-over traffic needs its own rate limiting and health monitoring, not unlimited trust.

Delivery Tracking / Webhooks

What's the difference between SENT and DELIVERED?

Strong answer angle

SENT (or PROVIDER_ACCEPTED) means the provider took the request; DELIVERED means confirmed receipt by the recipient/device, typically learned later via a callback or status query. Conflating the two overstates what the system actually knows.

Likely follow-ups
  • Does every channel support a real DELIVERED signal? — Tests honesty about channel differences — SMS in particular can often only be confirmed as far as carrier handoff, not true device receipt.

A delivery webhook arrives twice — what happens?

Strong answer angle

Deduplicate on the provider's own callback/event ID before applying any state transition, so a duplicate callback is a no-op rather than reapplying the same transition. See Architecture Deep Dive -> Provider Webhooks & Delivery Reconciliation.

Likely follow-ups
  • What if the webhooks arrive out of order? — Tests whether the candidate's state machine is monotonic (only allows forward progress) so an out-of-order callback can't regress an already-terminal state.

A callback never arrives for some notifications — how does support determine actual status?

Strong answer angle

Reconcile via the provider's own status/query API on a reasonable polling cadence for notifications with no callback after some window, rather than assuming silence means success or failure. See Failure & Scale -> Duplicate/Out-of-Order Webhooks.

Likely follow-ups
  • Should this reconciliation run continuously or only on demand? — Tests operational thinking — a background reconciliation job is more robust than only checking when a support ticket comes in.

Preferences & Push

Where do notification preferences belong in the pipeline?

Strong answer angle

Resolved as part of routing, before a channel worker acts — notification intent -> recipient policy/preferences -> eligible channel(s) -> channel queue/worker. See Architecture Deep Dive -> Preferences & Channel Eligibility.

Likely follow-ups
  • What if the preference changes while the notification is already queued? — Tests whether the candidate re-checks eligibility close to actual delivery for delayed/retried notifications, rather than trusting a preference snapshot taken at enqueue time.

A user has three devices — how does push delivery handle that?

Strong answer angle

Track tokens per device (user_id -> device_id -> platform -> push_token -> status), select currently active tokens at send time, and deactivate any token the provider reports as invalid. See Architecture Deep Dive -> Push Device Token Management.

Likely follow-ups
  • What happens if one of the three tokens is invalid? — Tests whether the candidate treats this as a per-token, non-retryable failure that shouldn't block delivery to the user's other active devices.

Campaign / Scale

Send 100M promotional notifications without delaying OTP/security traffic — how?

Strong answer angle

Chunked, paced fanout for the campaign into the normal channel/priority queues, with transactional/security traffic isolated in its own priority tier or queue so campaign volume physically cannot crowd it out. See Architecture Deep Dive -> Batch/Campaign Fanout and Failure & Scale -> Massive Campaign Overload.

Likely follow-ups
  • How do you calculate how long the campaign will take to fully deliver? — Tests the drain-time calculation: recipient count divided by sustainable delivery rate (itself bounded by provider quota), not an assumption that more workers linearly speeds this up.

What if arrival rate exceeds delivery rate for hours, not just a temporary burst?

Strong answer angle

That's no longer a queue-absorption problem — it's a genuine capacity mismatch. The queue keeps growing regardless of tuning; the actual fix is provider-capacity increase, shedding/deprioritizing lower-value traffic, or accepting a longer delivery window for that traffic explicitly.

Likely follow-ups
  • Is provider quota or worker count usually the bottleneck here? — Tests whether the candidate defaults to 'add more workers' or correctly identifies provider quota as the likely hard ceiling.

Data Consistency

The database write for a new notification succeeds, but the queue publish fails — what now?

Strong answer angle

This is the DB/queue dual-write problem: the notification exists in state but no worker will ever see it, unless something detects and repairs the gap. See Architecture Deep Dive -> Durable Notification Ingestion.

Likely follow-ups
  • How does the transactional outbox pattern help here? — Tests whether the candidate can explain the mechanism: write notification + outbox row in one transaction, then a separate publisher retries publishing until it succeeds.

Can the outbox publisher itself produce duplicates?

Strong answer angle

Yes — if it publishes successfully but crashes before marking the row published, it may re-publish the same event on restart. This is exactly why consumers must still be idempotent even with an outbox in place.

Likely follow-ups
  • So does the outbox eliminate the need for consumer idempotency? — Tests a subtle but important point: no — the outbox solves the dual-write problem, not exactly-once delivery to the broker or beyond.

Failure Curveballs

The message broker/queue itself is unavailable — what happens to the API?

Strong answer angle

If durable acceptance can't be guaranteed (e.g. no outbox buffering the write), the API may need to degrade honestly rather than falsely claim acceptance. With a transactional outbox, the database write can still succeed and accumulate safely until the broker recovers. See Failure & Scale -> Queue/Broker Outage.

Likely follow-ups
  • What happens to notifications submitted during the outage once the broker recovers? — Tests whether the candidate paces the recovery publish rather than releasing an instant flood.

The idempotency/dedup store is unavailable — do you keep sending?

Strong answer angle

Depends on notification class: pause high-risk/high-consequence sends, or accept elevated duplicate risk for lower-risk classes, or lean on provider-side idempotency where supported as a fallback layer. See Failure & Scale -> Dedup/Idempotency Store Failure.

Likely follow-ups
  • Is there one universal right answer here? — Tests whether the candidate resists giving a single blanket answer and instead reasons per notification class.

The delivery tracker is unavailable — does delivery stop?

Strong answer angle

It shouldn't have to — if the tracker is a downstream, best-effort-buffered consumer rather than a synchronous dependency, delivery can continue while status updates are buffered and retried once the tracker recovers. See Failure & Scale -> Delivery Tracker Failure.

Likely follow-ups
  • What data must be preserved during the outage to avoid a permanent gap? — Tests whether the candidate names provider IDs/results specifically as data that must not be lost even while buffered.

You see an elevated 5xx rate from a provider — is that the same as an outage?

Strong answer angle

Not necessarily — it could be partial degradation rather than full unavailability. The response should be proportional: back off and reduce assumed capacity, but not necessarily fail over immediately, and keep monitoring whether the rate is climbing toward a full outage or stabilizing.

Likely follow-ups
  • How would you distinguish partial degradation from a full outage in your monitoring? — Tests whether the candidate thinks in terms of graduated signals (error rate trend, latency percentiles) rather than a binary up/down state.

The webhook receiving endpoint itself goes down — what happens to reconciliation?

Strong answer angle

Missed callbacks during that window should be recovered by later reconciling against the provider's own status/query API, if one exists, rather than treating the gap as permanently lost information. See Architecture Deep Dive -> Provider Webhooks & Delivery Reconciliation.

Likely follow-ups
  • Would the provider retry the webhook on its own? — Tests awareness that many providers do retry webhook delivery for a period, which is itself a source of the duplicate-callback scenario, not just a separate concern.

A single tenant/service starts flooding the API with an unusually high notification volume — what protects the rest of the system?

Strong answer angle

Per-tenant/per-producer rate limiting or quotas at the API layer, independent of the downstream provider rate limiting — otherwise one misbehaving or buggy producer can consume disproportionate queue and provider capacity that other tenants depend on.

Likely follow-ups
  • Is this the same mechanism as provider rate limiting? — Tests whether the candidate distinguishes tenant-level ingestion limiting (protecting fairness among producers) from provider-level rate limiting (protecting the external provider's quota) as two separate, complementary controls.