Scott Shi
因外部通道缺乏两阶段提交导致端到端精确一次投递在工程上不可行,高并发通知系统必须在持久化后立即返回异步回执并通过关系型状态机驱动重试。
AI-assisted summary of [Chinese] Mock Interview: design notification system — original source. Check the original for full context.
Source published:
Key ideas
- For a platform scaling to 200 million daily active users averaging 5 alerts per day, notification ingestion reaches an average of 11,600 write QPS, necessitating horizontal scaling and asynchronous decoupling from third-party vendor networks. (33:23)
- Strict end-to-end exactly-once delivery across external gateways like SendGrid or Twilio is practically infeasible due to the absence of distributed two-phase commit protocols, making durable at-least-once delivery with receiver deduplication the standard architectural pattern. (24:26)
- Storage is bifurcated into high-throughput NoSQL (DynamoDB) for immutable raw message bodies and relational SQL for transactional delivery lifecycle state tracking and stuck-message detection. (72:40)
- To prevent HTTP socket timeouts and message loss during massive broadcasts, the ingestion API immediately commits payloads to durable storage and returns an asynchronous tracking receipt ID before worker pools handle downstream dispatch. (67:56)
Detailed notes
## Background Published on YouTube by Scott Shi on November 29, 2020, this video records a comprehensive mock system design interview. The description notes that the session covers designing a notification system and provides a link to a collaborative working document used during the exercise.
## TL;DR The interview begins with a deep dive into the candidate's past payment storage migration project at Uber Money, followed by coaching on framing technical challenges around business impact. The main exercise entails designing an enterprise-scale notification platform supporting email, SMS, mobile push, and phone calls for 200 million daily active users. Together, the interviewer and candidate establish an asynchronous architecture that decouples immutable message payloads in DynamoDB from transactional lifecycle state tracking in a relational database. The discussion thoroughly analyzes delivery semantics, explaining why third-party exactly-once delivery is impractical and demonstrating how adapter layers normalize external provider errors and handle retries.
## Atmosphere & style The session has an authentic, collegial, and rigorous engineering tone that balances high-stakes interview realism with practical senior-level mentorship. Scott seamlessly transitions between probing interviewer and encouraging coach, interjecting with good humor to correct arithmetic slips and steer the candidate away from unnecessary edge over-engineering. It feels like an illuminating behind-the-scenes peer review, highlighting how architectural trade-offs, state machines, and distributed failure modes are actually evaluated in top-tier tech interviews.
## Bulletin - [33:23](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2003s) For a platform scaling to 200 million daily active users averaging 5 alerts per day, notification ingestion reaches an average of 11,600 write QPS, necessitating horizontal scaling and asynchronous decoupling from third-party vendor networks. - [24:26](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1466s) Strict end-to-end exactly-once delivery across external gateways like SendGrid or Twilio is practically infeasible due to the absence of distributed two-phase commit protocols, making durable at-least-once delivery with receiver deduplication the standard architectural pattern. - [01:12:40](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4360s) Storage is bifurcated into high-throughput NoSQL (DynamoDB) for immutable raw message bodies and relational SQL for transactional delivery lifecycle state tracking and stuck-message detection. - [01:07:56](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4076s) To prevent HTTP socket timeouts and message loss during massive broadcasts, the ingestion API immediately commits payloads to durable storage and returns an asynchronous tracking receipt ID before worker pools handle downstream dispatch.
## Key takeaways - [16:35](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=995s) Senior candidates should frame past engineering projects around architectural ownership, explicit trade-offs, and measurable business impact rather than low-level implementation details or technical jargon. - [14:01](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=841s) Leveraging inherent business TTLs allows zero-downtime database migrations without heavy batch backfill pipelines by letting old records naturally expire after cutting off writes. - [24:26](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1466s) True end-to-end exactly-once delivery across autonomous third-party APIs is an unsolved distributed dilemma; external network timeouts prevent definitive state verification without unsupported 2PC/XA protocols. - [29:58](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1798s) A hybrid push-pull architecture is essential: connected clients receive real-time push events, whereas backgrounded or disconnected clients pull accumulated unread notifications via RPC/gRPC upon reconnecting. - [29:58](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1798s) Global notification ordering can be relaxed, allowing traffic to be segmented into urgent transactional alerts and delay-tolerant low-priority engagement updates. - [37:09](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2229s) Payload pipelines should never transmit heavy binary multimedia directly; messages must carry lightweight identifiers or HTML references that resolve assets lazily through CDNs. - [51:20](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3080s) Internal infrastructure services operating within a private corporate network do not require public edge security overhead such as external SSL termination or public client authentication tokens. - [56:48](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3408s) Asynchronous worker pools are critical to decouple the ingestion layer from erratic downstream provider latency and rate-limiting bottlenecks. - [01:07:56](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4076s) Issuing an asynchronous tracking receipt token must occur strictly after database persistence; acknowledging requests beforehand creates data-loss vulnerabilities if nodes crash mid-flight. - [01:12:40](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4360s) Immutable notification message bodies should be separated from mutable delivery states to keep large payload blobs out of transactional relational tables. - [01:18:16](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4696s) A relational metadata schema tracking recipient delivery states with an updated timestamp enables reconciliation jobs to detect and retry messages stranded in non-terminal states. - [01:32:00](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=5520s) Downstream provider adapters must translate vendor-specific errors into standardized internal status codes to prevent coupling core retry engines to provider-specific nuances. - [01:32:00](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=5520s) Multi-recipient batch broadcasts must record granular delivery states per recipient to avoid re-dispatching alerts to users who already received them when partial batch failures occur.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=0s) Pre-Interview Warm-Up and Distributed Systems Concepts
Before the interview formally gets underway, the candidate spends several minutes reviewing distributed systems theory and setting up technical logistics. Muttering through fundamental database and concurrency concepts, the candidate mentally revisits linearizability, master-slave replication, consensus protocols, snapshot isolation, and compare-and-set operations. The discussion touches upon whether serializability or snapshot isolation strictly guarantees linearizability, reinforcing the rigor needed for high-stakes distributed architecture. After troubleshooting screen sharing and access permissions for the interview document, the interviewer and candidate confirm audio and visual feeds, ready to begin the session.
- Key theoretical topics reviewed: linearizability, compare-and-set (CAS), consensus requirements, read/write snapshot isolation, and serializability guarantees. - Interview logistics: granting Google Doc permissions and sharing the screen for the collaborative whiteboarding exercise.
---
[10:20](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=620s) Past Project Deep Dive: Uber Money Payment Storage Migration
The interviewer kicks off the session by asking the candidate to spend roughly five minutes detailing the most technically challenging and impactful project from their professional career, specifically excluding school assignments. The candidate introduces their background on the Uber Money team, handling payments infrastructure. Their most technically demanding project over the past year was migrating the payments "grant storage" away from a legacy standalone DynamoDB cluster into Uber's centralized LedgerStore Gateway. This engineering initiative took about three months to execute and required strict SLA negotiations with downstream platform teams alongside a zero-downtime, lossless data migration pipeline.
When pressed on what constituted the hardest engineering challenge of the migration, the candidate explains the delicate fallback and ordering strategies needed during dual writing and reading. To guarantee zero data loss, the team first routed writes to the new LedgerStore Gateway before falling back to the legacy grant store, subsequently reversing the operational precedence once consistency was verified. Extensive online validation logic had to be authored to continuously reconcile records and confirm that data was identical across both storage engines before retiring the old database.
```mermaid flowchart LR accTitle: Dual-Write Migration Architecture accDescr: Migration pipeline showing shadow writes, validation checks, and cutover between DynamoDB and LedgerStore Gateway.
subgraph ClientLayer["Write Traffic"] A["Payment Ingestion Service"] end
subgraph StorageLayer["Dual-Write & Fallback"] B["LedgerStore Gateway<br/>(New Primary)"] C["Legacy DynamoDB<br/>(Grant Store)"] D{"Validation Engine<br/>& Shadow Reconciler"} end
A -->|"1. Primary Write"| B A -->|"2. Fallback / Shadow Write"| C B -.-> D C -.-> D ```
- System components migrated: standalone DynamoDB to internal LedgerStore Gateway. - Project timeline: approximately 3 months. - Core technical hurdle: lossless dual-write sequencing, dynamic fallback routing, and real-time shadow validation checks.
---
[14:01](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=841s) Project Ownership and Handling Historical Data Expiration
The interviewer probes further into the candidate’s personal autonomy and architectural decision-making, asking whether they drove the effort independently or worked under heavy mentorship, and how historical records were migrated. The candidate clarifies that while they referenced prior partial migrations and consulted internal staff engineers on tricky edge cases, the day-to-day execution and design were their own responsibility.
Regarding historical data backfills, the candidate reveals an elegant operational shortcut: they did not need to run heavy batch backfill pipelines such as Apache Airflow or Piper jobs. The underlying payment grant records possessed a natural business expiration window of six days. By setting a hard timestamp cutoff, freezing writes to legacy storage, and letting the existing six-day TTL elapse, the historical data in the old DynamoDB instances naturally expired without requiring complex offline extract-transform-load (ETL) backfills. The interviewer acknowledges this approach as a practical, low-risk way to sidestep cumbersome backfill scripts.
- Role and ownership: authored core migration logic with guidance from staff engineers; leveraged architectural patterns from previous partial projects. - Historical data strategy: avoided custom batch/Airflow jobs because grant records carried a strict 6-day TTL. - Transition execution: writes to the legacy store were halted before the deadline, allowing old records to age out cleanly.
---
[16:35](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=995s) Interviewer Coaching: Articulating Scope and Business Impact
At this juncture, the interviewer steps out of character to deliver constructive feedback on how senior candidates should pitch technical war stories in interviews. The interviewer emphasizes that interviewers listen closely for independent problem-solving skills, engineering maturity, and crisp articulation. Rather than framing work as merely implementing a task or building an Airflow pipeline that someone else specified, candidates must claim ownership of the end-to-end design, explaining *why* the architectural choices were made and contrasting them with rejected alternatives.
Furthermore, the interviewer cautions against getting bogged down in low-level technical jargon—humorously labeled as "stop words" or compiler-level minutiae like garbage collection details—unless they are directly tied to real business value. Candidates need to connect their technical feats to organizational scale: replacing legacy dispatch infrastructure, deprecating fragile microservices, improving uptime, or safeguarding multi-million dollar payment flows. Framing technical challenges around measurable business impact elevates the candidate from a routine implementer to a high-caliber system architect.
- Core pitch guidance: shift framing from "implementing someone else's flow" to owning system-level problem solving and architectural decisions. - Articulation tips: cut out irrelevant technical jargon ("stop words") and highlight high-level engineering tradeoffs. - Business value alignment: explicitly quantify system impact (e.g., replacing monolithic legacy systems, mitigating financial risk, slashing latency).
---
[20:29](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1229s) System Design Prompt: Enterprise Notification Platform
The interview transitions into the primary system design exercise: designing a multi-tenant, enterprise-scale notification platform capable of handling diverse notification types across the company. The interviewer outlines the functional scope: the system must support mobile in-app push notifications (Apple APNs and Google FCM), email alerts via vendors like SendGrid, and SMS or voice messaging via providers like Twilio. Because downstream vendors can fail or rate-limit requests, robust third-party integrations with fallback mechanisms are mandatory.
Additionally, the interviewer establishes strict delivery semantics and operational visibility requirements. The platform must offer unified access—either via RESTful HTTP endpoints or Kafka message queues—for any internal upstream team. Upstream callers must be able to specify delivery guarantees, choosing between at-least-once, at-most-once, and potentially exactly-once delivery. The interviewer points out that delivery guarantees carry serious legal and monetary weight, noting that "LinkedIn was fined millions of dollars because it sent promotional emails repeatedly" to users in 2014 and 2015. Finally, the system must feature business-centric observability: not just raw CPU and memory metrics, but live operational dashboards tracking in-flight notifications, delivery progress, and vendor-specific queue backlogs (such as flagging 100 email notifications stalled on SendGrid).
```mermaid flowchart TD accTitle: Notification Platform Scope Overview accDescr: Upstream clients communicating through a unified interface into delivery engines and vendor integrations.
subgraph Upstream["Internal Services"] U1["Order Service"] U2["Marketing Campaign"] U3["Payment Service"] end
subgraph Platform["Notification System"] API["Unified Interface<br/>(REST API / Kafka Topics)"] Engine["Notification Router &<br/>Delivery State Machine"] Dash["Business Dashboard<br/>(Backlog & Vendor Health)"] end
subgraph Vendors["Downstream Delivery Channels"] V1["Apple APNs / Google FCM<br/>(In-App Push)"] V2["SendGrid<br/>(Email)"] V3["Twilio<br/>(SMS / Voice)"] end
U1 --> API U2 --> API U3 --> API API --> Engine Engine -.-> Dash Engine --> V1 Engine --> V2 Engine --> V3 ```
- Supported channels: iOS/Android native push, email (SendGrid), SMS/phone calls (Twilio). - Ingestion interfaces: unified REST API endpoints and asynchronous Kafka message topics. - Delivery guarantees: configurable modes including at-least-once and at-most-once delivery. - Observability mandate: business dashboards displaying real-time message state, pipeline progress, and per-vendor bottleneck counts.
---
[24:26](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1466s) Scoping Requirements and Clarifying Exactly-Once Semantics
The candidate assumes control of the whiteboard, structuring the problem into business use cases, operational constraints, API contracts, architectural topologies, and data models. Under the MVP scope, the candidate defines multi-channel delivery across mobile apps, email, and SMS, incorporating configurable delivery modes. When discussing delivery semantics, the candidate associates at-least-once with persistent retry queues and durable storage, whereas at-most-once represents a fire-and-forget mechanism where dropped packets bypass retries. For exactly-once, the candidate suggests upstream idempotency filtering at the load balancer layer to deduplicate identical client submissions before processing.
The interviewer steps in to clarify the subtle theoretical distinction between distributed streaming deduplication (such as Kafka’s internal exactly-once semantics) and end-to-end exactly-once delivery across uncoordinated third-party networks. In a notification platform, an internal worker may call an external vendor API like SendGrid or Twilio; if that third-party request experiences a network timeout or drops the response packet, the internal system cannot definitively know whether the vendor dispatched the message. True end-to-end exactly-once delivery across autonomous external systems is an unsolved two-phase commit (XA distributed transaction) dilemma. Because third-party vendors do not support two-phase commits, the interviewer advises treating strict end-to-end exactly-once guarantees as practically infeasible and suggests keeping it out of the core MVP requirements.
- Candidate's structural roadmap: use cases & MVP features $\rightarrow$ constraints $\rightarrow$ API contracts $\rightarrow$ architectural design $\rightarrow$ data storage schema. - Delivery semantics analysis: - At-least-once: backed by durable message stores and backoff retry queues. - At-most-once: lightweight fire-and-forget without persistence or retry overhead. - Exactly-once: constrained by third-party black-box API behavior, where network timeouts make distributed state verification impossible without 2PC/XA protocols. - Architectural decision: scope out rigid third-party exactly-once guarantees from the MVP, prioritizing robust deduplication and at-least-once delivery instead.
[29:58](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1798s) Push vs. Pull Models, Recurring Alerts, and Latency Expectations
The candidate outlines core MVP and bonus capabilities, focusing on how notifications reach users depending on client state. A distinction is drawn between active and idle subscribers. When a user has their application open and active, notifications can be pushed directly in real time. However, when an app is closed, suspended on iOS, or otherwise disconnected, pushing may fail or get delayed. For these scenarios, the candidate proposes a pull model: once the user reopens the app or background app refresh triggers, the client polls the notification service via an RPC or gRPC endpoint to fetch accumulated unread alerts directly from the data store.
The discussion also touches on scheduling and recurring notifications, which are critical for marketing emails, calendar reminders, and SMS alerts. Moving into non-functional constraints and non-goals, the candidate emphasizes that strict real-time latency is not universally required. Traffic splits into priority tiers: while critical operational alerts must arrive promptly, low-priority engagement updates (such as routine Twitter activity feeds) frequently experience delays of several hours without degrading the core user experience. Furthermore, strict chronological ordering across distinct notifications does not need to be guaranteed by the messaging pipeline.
- **Push Mechanism:** Direct delivery to active, foregrounded client sessions. - **Pull Mechanism:** On-demand RPC/gRPC polling upon client relaunch or background task execution. - **Latency Tiers:** High-priority (urgent/transactional) delivered quickly; low-priority (feed updates, promotions) tolerated with multi-hour lag. - **Ordering:** Out-of-order delivery is acceptable across independent notifications.
---
[33:23](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2003s) Scale Estimation: Sizing for 200 Million Daily Active Users
Noticing that throughput constraints have not yet been framed, the interviewer intervenes with concrete sizing requirements: "Let's assume that we are designing the notification system for Facebook... you have 200 million active users per day, and each user has five notifications per day on average." A lighthearted moment ensues as the candidate begins writing out the math and the interviewer gently points out a clerical error: "You're missing a zero."
$$\text{Total Daily Volume} = 200{,}000{,}000 \text{ users} \times 5 \frac{\text{notifications}}{\text{user}\cdot\text{day}} = 1{,}000{,}000{,}000 \text{ notifications/day}$$
$$\text{Average QPS} = \frac{1{,}000{,}000{,}000}{24 \times 3600 \text{ seconds}} \approx 11{,}574 \text{ requests/sec}$$
With average write traffic hovering near 11,600 QPS and peak traffic predictably surging much higher, the candidate deduces that notification ingestion and delivery cannot reside on a single host. The system demands horizontal scalability across the ingestion tier and storage sharding to handle write-heavy fan-outs without hitting host-level I/O bottlenecks. In addition to high availability and horizontal scalability, the design must prioritize flexibility to decouple internal callers from end-user channel protocols.
---
[37:09](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2229s) Message Formats, Multimedia Payloads, and Channel Topics
The conversation pivots to how messages are structured and which communication channels the notification platform must accommodate. The candidate initially considers restricting the scope to pure plain-text payloads to simplify data transfer. The interviewer pushes back slightly, noting that modern notification systems—particularly for emails—frequently handle rich media: "If you're going to send emails, you're going to have images, right?... and you may have possibly video, but most likely you're going to be an HTML file like multimedia."
The candidate agrees to treat rich media as an embedded reference model. Rather than transmitting raw binary blobs across the notification ingestion pipeline, the message payload remains a clean text or HTML document containing reference identifiers (such as image or video IDs). Downstream client renderers resolve and hydrate these media assets lazily via CDNs. The interviewer and candidate agree on four core delivery channels that the system must support:
- **SMS:** Short, text-focused, latency-sensitive mobile carrier messages. - **Email:** Rich HTML formatting referencing externally hosted multimedia assets. - **Phone Calls:** Automated voice notifications delivering pre-recorded audio clips to keep voice-gateway integration straightforward. - **In-App:** Direct feed updates and real-time pop-ups targeting connected native applications.
---
[40:44](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2444s) API Design: Topics, Templates, and Delivery Semantics
Moving into the API contract, the candidate drafts a set of endpoints centered on a pub/sub paradigm:
``` createTopic(topicId, topicName, serviceType, metadata) send(topicId, sendMode) subscribe(topicId, subMode) ```
The interviewer seeks clarification on terminology, asking whether "topic" implies a raw Apache Kafka topic. The candidate clarifies that here, a topic acts as a business-level notification campaign or reusable template. For instance, an internal marketing service running an ad campaign registers a template with a specific name, service type (`in-app`), priority flags, and security tokens. The interviewer synthesizes the concept: "Oh, I see. So you're referring to more like a notification template, right? Like an email template."
When pressed on what `sendMode` and `subMode` signify, the candidate explains that `sendMode` defines delivery semantics—specifically choosing between at-least-once, at-most-once, or exactly-once delivery guarantees depending on business criticality. The candidate concedes that `subMode` is still an early, exploratory concept intended to allow subscribers to specify delivery priority or filter constraints, but agrees that the foundational three APIs provide a workable base for topic registration, dispatch, and channel listening.
---
[45:33](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2733s) Pub/Sub Modeling Debate and High-Level Architecture Walkthrough
The interviewer raises a structural design concern regarding how the candidate's pub/sub topic model handles targeted, multi-service routing: "I'm a little confused... could you please explain how your system is going to model the following scenario? Like Service A sends SMS to User 1, Service B sends email to User 2, and Service C sends phone calls to User 2?" The interviewer wants to know how individual user IDs and channel destinations map to generalized topics.
The candidate explains that internal producer services do not communicate directly with external providers or maintain user channel states themselves. Instead, internal services call the notification platform, which evaluates caller intent, retrieves message templates, and routes requests to appropriate sender adapters. The candidate sketches the end-to-end data flow: incoming traffic hits an external Load Balancer, reaches service workers that fetch metadata from a Metadata Store, places dispatches into intermediate temporary message queues, and hands jobs off to sender workers configured with specialized adapter layers for SMS, email, telephony, and mobile push.
```mermaid flowchart LR accTitle: Notification System High-Level Architecture accDescr: Flow diagram showing request routing from producer clients through a load balancer into metadata and temporary queue storage, finishing at downstream sender adapters.
subgraph Ingestion ["Ingestion Layer"] C["Producer Services"] --> LB["Load Balancer"] LB --> GW["Notification Gateway Service"] end
subgraph Storage ["Storage & Queueing"] GW --> MS[("Metadata Store\nTemplates & Configs")] GW --> QS[("Temp Storage / Queue\nPending Messages")] end
subgraph Dispatch ["Dispatch & Adapters"] QS --> SW["Sender Worker Engine"] SW --> AD_SMS["SMS Adapter"] SW --> AD_EM["Email Adapter"] SW --> AD_PH["Phone/Voice Adapter"] SW --> AD_AP["In-App Push Adapter"] end ```
[49:07](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2947s) Clarifying the Adapter Role and Client Lifecycle
The discussion opens with the candidate clarifying the role of the adapter layer, noting that it acts as the interface bridging the internal pipeline to external delivery channels such as SMS, email, and mobile push notifications. The interviewer agrees with this framing, observing that the adapter essentially serves as a third-party integration interface. Tracing the overall pipeline, the interviewer summarizes the baseline architecture: the system ingests a notification request, queries metadata, writes the payload to an interim message store for durability, and dispatches it through a sender layer to adapters that reach end users.
```mermaid flowchart LR accTitle: Notification Dispatch Pipeline accDescr: High-level overview of message dispatch through temp storage, senders, and adapters to third-party endpoints. subgraph Ingestion["Ingestion & Durability"] direction TB C["Client Service"] --> AP["API Gateway"] AP --> MD["Metadata Service"] AP --> TS["Temp Storage (DynamoDB)"] end subgraph Processing["Dispatch & Delivery"] direction TB TS --> SW["Sender Workers (Async)"] SW --> AD["Adapters"] AD --> EP["Push / SMS / Email"] end subgraph Observability["Event Stream"] SW -.-> KF["Kafka + Flink"] end ```
The interviewer then raises an operational question regarding client communication: should the caller be kept waiting on an open connection throughout this multi-step broadcast, or how will the system respond? To frame the perspective, the candidate conceptualizes the client using a high-fanout Twitter analogy: "for example, Trump send a tweet here, I think Trump is the client, and the sender go all the way to different SMS, it means that all the followers of Trump would receive the SMS or the tweets." The interviewer refocuses the prompt on API design and protocol mechanics, asking what the end-to-end request lifecycle looks like from that client's point of view.
[51:20](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3080s) Edge Architecture vs. Internal Service Clarification
Addressing the lifecycle, the candidate initially describes a full-featured perimeter ingress setup: a reverse proxy decrypting HTTPS payloads, a normalization layer converting requests into an internal format, local caching to resolve topic identifiers against a Metadata Service, and local disk logging to emit metrics for a real-time progress dashboard. The interviewer pushes back on coupling disk metrics and logs directly to the dashboard path, prompting a pause while the candidate adjusts the data path around storage durability, Flink stream processing, and replication.
The candidate then attempts to place authentication and authorization checks after the load balancer, which prompts the interviewer to intervene and correct the baseline assumptions of the system scope. The interviewer explains that this notification system is strictly an internal infrastructure platform: "I think all the request is actually internal service... like you build a tool for all your company's service." If designing for a company like Facebook, callers such as "Service A" or "Service B" are internal microservices already authenticated on the private network. The candidate immediately acknowledges the pivot, realizing that public edge authentication and perimeter reverse proxy decryption are unnecessary overhead for trusted internal services.
- **Client Nature**: Internal services within the enterprise infrastructure (e.g., Service A notifying User 1) rather than unauthenticated public mobile/web clients. - **Security Boundary**: Mutual trust inside the private internal network eliminates the need for edge auth tokens and SSL offloading at this layer. - **Monitoring Architecture**: Ingestion telemetry is funneled through Kafka and Apache Flink to calculate aggregation metrics for the operational dashboard rather than scraping raw application disk logs.
[56:48](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3408s) Storage Selection and Asynchronous Worker Design
With the internal boundary established, the candidate restructures the ingest fan-out into three parallel responsibilities: querying the Metadata Service for the target topic definition, persisting the message in a Temporary Storage Service so that inactive or idle subscribers can retrieve updates upon reconnecting, and streaming telemetry events into Kafka and Flink for real-time monitoring.
For the Temporary Storage Service, the candidate chooses a scalable key-value database, specifically selecting Amazon DynamoDB to sustain high read and write throughput during high-traffic notification spikes. The sender component is designed as an elastic worker pool managing WebSocket connections and dispatch tasks, decoupling message generation from delivery. The adapter acts as a dynamic routing layer that delegates each payload to carrier-specific endpoints like APNs, FCM, or SMS gateways.
The interviewer praises the decision to use an asynchronous worker pool, advising the candidate to explicitly highlight core architectural buzzwords like "asynchronous" execution and "separation of concerns" to demonstrate seniority. The candidate agrees, noting down asynchronous worker pools as the linchpin for keeping the ingest layer responsive.
[59:08](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3548s) Defining the Message and Request Lifecycle
Before detailing the DynamoDB schema, the candidate remembers that the functional requirements demand an "at-least-once" delivery guarantee, necessitating a dedicated Retry Service to handle failed outbound attempts. The interviewer steps in to guide the conversation toward concrete API mechanics: "Once you finish out the design, I do want to spend five minutes explain the life cycle for a message, a life cycle for notification, and life cycle for a client request."
To ground the explanation, the interviewer provides a concrete scenario: Service A wants to send a notification to User 1. The interviewer asks for a numbered step-by-step breakdown: Step 1 is calling the API with metadata and message content, but what exactly does Step 2 do, and how does the conversation proceed?
The candidate walks through the chronological path: 1. The client invokes the ingestion API endpoint with `topic_id`, payload, and delivery policy. 2. The service pre-processes the request through internal routing and retrieves schema definitions from the Metadata Service. 3. The notification payload is written durably into the Temporary Storage Service. 4. Sender workers poll or receive the persisted messages to initiate dispatch. 5. If a delivery attempt fails at the sender or adapter level, the message is routed to the Retry Service for exponential backoff and redelivery.
[01:03:04](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3784s) Asynchronous Acknowledgement and Status Transitions
The interviewer presses on a fundamental distributed systems dilemma: "When do I know my message is sent, and when do I know my message is received by you?" These represent distinct milestones that must never be conflated. The candidate notes that a message is officially "recorded" once written to the Temporary Storage Service, whereas it is only truly "sent" after the adapter layer receives a confirmed delivery acknowledgement from the end subscriber device or upstream third-party gateway.
```mermaid flowchart TD accTitle: Message Delivery State Transitions accDescr: State machine of an internal notification request from pending through completed or failed. P["Pending<br>(API Request Ingested)"] --> S["Sending<br>(Persisted to Temp Storage)"] S --> C["Completed<br>(All Adapters Acknowledge)"] S --> PC["Partially Completed<br>(Partial Adapter Success)"] S --> F["Failed<br>(Exhausted Retries / Storage Error)"] ```
When the interviewer asks whether a caller should block for minutes while millions of fan-out messages are pushed across the network, the candidate confirms an asynchronous status model with three primary states: - **Pending**: The API has received the request, but it has not yet been validated or committed. - **Sending**: The message is safely persisted to DynamoDB, and asynchronous sender workers are actively dispatching notifications. - **Completed**: All intended recipients have received their notifications. If a subset fails or downstream systems error out, status flags transition to **Partially Completed** or **Failed**.
The interviewer validates this state machine and emphasizes that the synchronous API call must return immediately at the point of persistence: once Step 3 commits the record to Temporary Storage, the API returns an HTTP acknowledgement receipt containing a job or message ID with a status of `Sending`. The client is thus freed immediately, while the sender workers and retry pipelines asynchronously fulfill delivery in the background.
[01:07:56](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4076s) Asynchronous Polling, Receipt IDs, and Failure Handling
The interviewer, Scott, steers the discussion toward request-response mechanics and connection lifecycle management. He points out that an API cannot simply leave an HTTP request hanging indefinitely while it waits for downstream third-party delivery: "You don't want to hold this request forever." Instead, once the ingestion workflow reaches Step 5, the service must immediately acknowledge the caller by returning a distinct receipt ID. The client application can then asynchronously poll this receipt to inspect the current state of delivery. When the candidate asks why the system cannot immediately respond during the initial "pending" stage, Scott explains the harsh realities of distributed reliability: until the message payload is safely persisted to storage, acknowledging it introduces the risk of data loss. If the worker fails, the server crashes, or the client unexpectedly drops the socket connection before persistence finishes, the message vanishes into thin air while the client remains stuck indefinitely assuming the request is still pending.
```mermaid flowchart TD accTitle: Ingestion and Receipt Polling Flow accDescr: Flow showing asynchronous ingestion from client to storage and subsequent status polling. subgraph Ingestion ["Ingestion Phase"] A["Client Request"] --> B["Ingestion Worker"] B --> C["Topic & Payload Storage"] C --> D["Return Receipt ID"] end subgraph Polling ["Async Verification"] D -.-> E["Client Polls Receipt ID"] E --> F["Metadata Service"] F --> G["Current Delivery Status"] end ```
The discussion solidifies the rule that receipts can only be dispensed after state transitions are committed to topic storage and the database. This boundary ensures durability before handing execution off to background workers that handle fan-out, template rendering, and downstream vendor transport.
- **Receipt Handshake**: Rather than blocking synchronously, the server persists the notification intent and immediately returns an asynchronous tracking token (`receipt_id`). - **Failure Mode Defense**: Premature acknowledgments before database persistence cause orphan states if a node crashes or a TCP socket drops mid-flight. - **Worker Hand-off**: Committing the record to topic storage unblocks background consumers to handle delivery pipelines independently of caller latency.
---
[01:12:40](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4360s) Data Schema: Temporary Message Storage with DynamoDB
Turning to the data schema, Scott and the candidate determine the division of labor between raw message storage and operational metadata. Drawing direct parallels to an earlier architectural review of Slack's backend (specifically a design discussed around May 26), Scott highlights that chat and notification backends routinely decouple raw content storage from message delivery state machines. For the temporary message storage service, a high-throughput NoSQL key-value store such as Amazon DynamoDB fits best. The discussion debates whether delivery status belongs in this raw payload table; they conclude it should not, because a single broadcast topic ID might fan out across thousands of recipients with distinct operational statuses, while the message payload itself remains immutable.
```mermaid flowchart LR accTitle: DynamoDB Temporary Storage Schema accDescr: Diagram illustrating the partition key, sort key, and attributes for message storage. subgraph KeyStructure ["Key Schema"] PK["Partition Key: message_id (UUID)"] SK["Sort Key: timestamp"] end subgraph Attributes ["Attributes"] T["topic_id"] S["sender_id"] P["payload / body"] end KeyStructure --> Attributes ```
The candidate constructs the primary key layout for DynamoDB, setting `message_id` as the partition key and a `timestamp` as the sort key to order events efficiently. The record retains the core communication payload: sender identity, topic classification, and message content. By treating this store as a fast, durable key-value cache, the system avoids burdening relational databases with large, short-lived payload blobs.
- **Storage Engine**: Amazon DynamoDB acts as the transient, high-throughput key-value engine for message payloads. - **Key Design**: Partition Key is defined as `message_id`, paired with `timestamp` as the Sort Key for chronological ordering. - **Separation of Concerns**: Immutable message contents (body, sender ID, topic ID) are strictly separated from mutable delivery states.
---
[01:18:16](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4696s) Data Schema: Relational Metadata Store for Lifecycle Tracking
While the message payload resides in NoSQL, the operational metadata requires strict consistency. Scott and the candidate agree that the metadata service should be backed by a relational SQL database because tracking the life cycle of notifications demands atomic transactions and rollback capabilities: "You need transaction, right?" The metadata table manages the progression of notifications from submission to recipient delivery, tracking the specific parameters that govern routing.
The relational schema stores `message_id`, `receiver_id`, `status`, `send_mode`, and `service_type` (e.g., SMS, APNS push, email). Crucially, Scott emphasizes including an updated timestamp field (`updated_at`): "So you know which message has been stuck at what time for how long." By maintaining relational guarantees on this metadata, downstream monitoring jobs can run reconciliation queries, detect messages stranded in non-terminal states, and trigger automatic retries without duplicating the message body.
| Column | Type | Description | |:--- |:--- |:--- | | `message_id` | UUID / VARCHAR | Foreign key referencing the raw payload in temporary storage | | `receiver_id` | VARCHAR / BIGINT | Target user or device destination identifier | | `service_type` | VARCHAR | Delivery channel (e.g., Email, Push Notification, SMS) | | `send_mode` | VARCHAR | Dispatch strategy (Immediate, Scheduled, or Batched) | | `status` | ENUM / VARCHAR | Operational state (`PENDING`, `SENDING`, `COMPLETED`, `FAILED`) | | `updated_at` | TIMESTAMP | Last state update time used for latency tracking and stuck-message detection |
---
[01:20:55](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4855s) Tracing the End-to-End Notification Lifecycle
Scott prompts the candidate to spend three minutes detailing the notification's internal lifecycle from its own perspective, distinguishing it clearly from the client's external request-response cycle: "Let's try to describe what's the life cycle for notification looks like." The process begins at notification creation, where a client application requests a message dispatch. The pipeline moves into experiment evaluation and template rendering—for example, resolving localized email templates, user preferences, and variant flags.
```mermaid flowchart TD accTitle: Notification State Lifecycle accDescr: State diagram showing transitions from creation to sending and completion. A["Notification Created"] --> B["Template & Experiment Resolution"] B --> C["Status: PENDING in Metadata"] C --> D["Dispatched to Provider / Adapter"] D --> E["Status: SENDING"] E --> F{"Vendor Response"} F -->|"200 OK"| G["Status: COMPLETED"] F -->|"Error / Timeout"| H["Status: FAILED / RETRY"] ```
Once the payload is constructed, the metadata service records the notification with an initial state of `PENDING`. When background notification dispatch workers pick up the task and invoke downstream carrier adapters, the metadata status transitions to `SENDING`. Once the external gateway (such as an email provider or APNs gateway) acknowledges delivery or responds with a definitive webhook, the worker finalizes the state transition to `COMPLETED` or `FAILED`. This finite state machine guarantees visibility into every phase of delivery.
- **Creation & Hydration**: Client inputs are merged with user preference profiles and dynamic email/push templates. - **Pending Phase**: The generated notification is persisted in SQL metadata with status `PENDING` before network transmission. - **Dispatch to Complete**: Status switches to `SENDING` during adapter execution and shifts to `COMPLETED` once provider delivery callbacks arrive.
---
[01:25:40](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=5140s) Interview Debrief: Delivery Guarantees, Worker Pools, and Distributed Failure Modes
As the mock interview moves into its retrospective feedback stage, Scott asks the candidate to evaluate their performance: "Which part you did good and which part you didn't do well?" The review immediately centers on delivery guarantees: the classic trade-off between at-most-once and at-least-once delivery. In any real-world distributed system, "every single step can fail"—from network timeouts and crashed worker nodes to unacknowledged broker receipts. Designing for at-most-once delivery requires committing state markers early so an interrupted task never repeats, but this risks dropping notifications. Conversely, at-least-once guarantees require idempotent message handling at the receiver side, paired with robust retry loops.
```mermaid flowchart TD accTitle: Distributed System Philosophy accDescr: Subgraph breakdown of core distributed systems patterns discussed during debrief. subgraph IngestionCore ["Separation of Concerns"] A["Sender-Side Ingestion"] --> B["Queue Buffering"] end subgraph ExecutionLayer ["Async Execution"] B --> C["Worker Pool Processing"] C --> D["Event-Driven Notification Router"] end ```
Scott praises the candidate's use of foundational distributed systems patterns: - **Separation of Concerns**: Dividing fast request ingestion from slow, unpredictable downstream network calls. - **Asynchronous Worker Pools**: Decoupling the front-facing API from third-party vendor latencies via dedicated queue consumers. - **Event-Driven Architecture**: Moving state updates across the system using discrete lifecycle events rather than tight, synchronous service coupling. - **Tiered Durability**: Maintaining transient payloads in elastic NoSQL storage while recording authoritative state transitions in ACID-compliant SQL metadata.
---
[01:32:00](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=5520s) Deep-Dive Feedback: Router-Adapter Architecture, Retries, and Status Transitions
In the final minutes of the session, Scott deep-dives into edge-case handling, routing mechanics, and interface boundaries. A key design element reviewed is the relationship between the notification router and provider adapters. Scott advises that the adapter layer must strictly abstract low-level communication protocols and translate vendor-specific error codes into standardized internal status codes. Downstream service providers frequently return partial failures—for example, rate-limiting HTTP 429s, invalid device tokens, or transient connection resets. If an adapter exposes provider-specific nuances directly to the core router, the retry logic becomes brittle and difficult to maintain.
```mermaid flowchart LR accTitle: Router-Adapter Architecture accDescr: Separation of notification router from carrier-specific adapters and error translation. R["Notification Router"] --> A1["Email Adapter (SendGrid)"] R --> A2["Push Adapter (APNs/FCM)"] R --> A3["SMS Adapter (Twilio)"] A1 --> E["Normalized Error Codes & Retries"] A2 --> E A3 --> E ```
Scott notes that notifications often travel in batches, requiring the system to handle chunked grouping and partial completions: a batch of 500 emails might succeed for 490 recipients while 10 fail due to expired addresses. The state machine must record these partial outcomes cleanly rather than marking the entire batch as an ambiguous error. The session concludes with Scott emphasizing how clear categorization of error statuses, centralized retry configuration, and clean adapter abstractions transform a naive notification script into a resilient, production-grade enterprise notification platform.
- **Normalized Status Codes**: Low-level vendor errors (e.g., Twilio 21211, SendGrid 400-series) must be translated by adapters into uniform internal statuses (`RETRYABLE_ERROR`, `FATAL_INVALID_RECIPIENT`). - **Partial Completion States**: Broadcast messages and chunked batches must track individual recipient delivery states to prevent infinite re-dispatching of already-delivered items. - **Configurable Retry Policies**: Backoff intervals, jitter algorithms, and dead-letter queue handoffs should be controlled centrally at the router layer rather than scattered across individual worker adapters.
## Notable quotes > [20:29](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=1229s) LinkedIn was fined millions of dollars because it sent promotional emails repeatedly — Scott Shi
> [33:23](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2003s) Let's assume that we are designing the notification system for Facebook... you have 200 million active users per day, and each user has five notifications per day on average. — Scott Shi
> [33:23](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2003s) You're missing a zero. — Scott Shi
> [37:09](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2229s) If you're going to send emails, you're going to have images, right?... and you may have possibly video, but most likely you're going to be an HTML file like multimedia. — Scott Shi
> [40:44](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2444s) Oh, I see. So you're referring to more like a notification template, right? Like an email template. — Scott Shi
> [45:33](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2733s) I'm a little confused... could you please explain how your system is going to model the following scenario? Like Service A sends SMS to User 1, Service B sends email to User 2, and Service C sends phone calls to User 2? — Scott Shi
> [49:07](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=2947s) for example, Trump send a tweet here, I think Trump is the client, and the sender go all the way to different SMS, it means that all the followers of Trump would receive the SMS or the tweets. — Candidate
> [51:20](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3080s) I think all the request is actually internal service... like you build a tool for all your company's service. — Scott Shi
> [59:08](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3548s) Once you finish out the design, I do want to spend five minutes explain the life cycle for a message, a life cycle for notification, and life cycle for a client request. — Scott Shi
> [01:03:04](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=3784s) When do I know my message is sent, and when do I know my message is received by you? — Scott Shi
> [01:07:56](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4076s) You don't want to hold this request forever. — Scott Shi
> [01:18:16](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4696s) You need transaction, right? — Scott Shi
> [01:18:16](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4696s) So you know which message has been stuck at what time for how long. — Scott Shi
> [01:20:55](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=4855s) Let's try to describe what's the life cycle for notification looks like. — Scott Shi
> [01:25:40](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=5140s) Which part you did good and which part you didn't do well? — Scott Shi
> [01:25:40](https://www.youtube.com/watch?v=D0bBMzxE5O0&t=5140s) every single step can fail — Scott Shi
## Mentioned Scott Shi, Uber, Uber Money, DynamoDB, LedgerStore Gateway, Apache Airflow, Piper, SendGrid, Twilio, Apple Push Notification service (APNs), Firebase Cloud Messaging (FCM), Apache Kafka, LinkedIn, Facebook, Twitter, Donald Trump, Apache Flink, Slack, Amazon DynamoDB
## Video description in this session, we mocked a system design interview
doc could be found here: https://docs.google.com/document/d/140n69Pw-xqoD3eeGzcv2WH_CDRbgYmYzV6OPHl6NzA8/edit?usp=sharing
[Source](https://www.youtube.com/watch?v=D0bBMzxE5O0)