System design interview practice: a notification service
Practise explaining a design by following one notification from acceptance to delivery, then making a dependency fail. The useful answer is a reasoned proposal under stated constraints, not a diagram with the largest number of services.
This is an original fictional exercise. Its traffic figures are assumptions for practice, not MockReps workload measurements or a particular employer's interview question.
The brief
Design a service that accepts notification jobs and sends them through an external email provider. Each job describes one recipient and one message. During a launch, incoming jobs may reach 1,000 per second for one minute. Normal traffic after the burst is 20 jobs per second. The provider allows your workers to complete 200 jobs per second in total.
Before designing, ask:
- Does “accepted” mean durably queued, or already delivered?
- How late may a message arrive? Is a five-minute backlog acceptable?
- Are duplicates tolerable? Can the provider deduplicate requests using an idempotency key?
- Must messages for the same recipient be ordered?
- Do urgent account messages need a separate capacity budget from bulk campaigns?
For this exercise, assume acceptance means durable storage, no global ordering is required, and temporary backlog is allowed. Treat the permitted delay and duplicate behaviour as unresolved product decisions to make explicit.
A small starting design
Use an authenticated intake API, a durable job store, a queue and a bounded pool of delivery workers. Validate requests and enforce caller limits before accepting them. Give each logical job an identifier that can also support idempotent retries by the caller.
Avoid accepting a job in the database and then silently losing the queue publish. One option is to write the job and an outbox record in the same database transaction. A relay publishes outbox entries to the queue and marks them processed. The relay may publish more than once, so consumers still need duplicate handling. Explain why this extra mechanism is justified by the durability requirement.
Workers claim work, call the provider and record the result. Keep recipient data out of logs; operational events can use a non-sensitive job reference. Define retention and access controls for the job payload rather than keeping notification content indefinitely.
Work through the capacity
Assume the queue starts empty and the provider sustains its full limit:
- During the burst, the backlog grows by 1,000 − 200 = 800 jobs each second.
- After 60 seconds, about 48,000 jobs remain queued.
- Once arrivals drop to 20 per second, spare capacity is 200 − 20 = 180 jobs per second.
- Draining that backlog takes about 48,000 ÷ 180 = 267 seconds, or 4 minutes 27 seconds, after the burst ends.
This simplified calculation excludes retries, worker downtime and uneven processing time. If the allowed delay is shorter, adding workers alone cannot bypass the provider limit. Discuss increasing provider capacity, admission control, campaign scheduling or separate priorities. Use queue age as well as queue length to judge whether users are waiting too long.
Explain the failures
The provider times out after accepting a message. A retry could send the message twice. A database flag alone cannot atomically coordinate your transaction with the provider's external send. Use a stable provider-supported idempotency key where available; otherwise state the duplicate risk and choose a recovery policy with the product owner.
A worker crashes after sending but before acknowledging the queue. The job may be delivered again. Queue acknowledgement, job state and provider idempotency must work together. Do not promise end-to-end exactly-once delivery merely because a queue has a deduplication feature.
The provider returns persistent errors. Bound retries, apply backoff with jitter, separate permanent failures and provide a controlled replay path. A dead-letter queue needs ownership and investigation; it is not a successful delivery destination.
Bulk traffic starves urgent messages. Reserve capacity or isolate queues where the requirements justify it. Explain the operational cost of that decision instead of adding priority mechanisms by default.
For a concrete queue reference, Amazon documents that standard SQS queues can deliver a message more than once. The broader design above is a worked proposal, not a claim that every queue or provider behaves identically.
Finish with a decision, not another box
Summarise the accepted guarantees, the largest unresolved risk and the measurement that would change your design. Then try a follow-up: what changes if per-recipient ordering becomes mandatory, or the provider limit halves?
Review whether you clarified scope, traced data flow, justified trade-offs and covered failures. Continue with the software-engineering practice plan, or explore MockReps technical workspaces and their feedback limitations.