To-do Reminders
Support private tasks with editable deadlines and reminders that survive restarts without sending obsolete schedules.
Support private tasks with editable deadlines and reminders that survive restarts without sending obsolete schedules. This worked design targets SDE-1; the scale figures below are exercise assumptions, not claims about a named company's implementation.
Clarify the prompt
| Ask the interviewer | Decision to record |
|---|---|
| One reminder or recurring reminders? | Recurrence requires timezone and daylight-saving policy. |
| Does completing or editing a task cancel pending delivery? | Define the race at dispatch. |
| What lateness is acceptable? | Use a measurable target rather than exact wall-clock delivery. |
Requirements and scope
The core deliverable is: Support private tasks with editable deadlines and reminders that survive restarts without sending obsolete schedules. The first design implements the APIs below and the state constraints in the data model. Agree the latency, freshness, retention and failure policy before adding infrastructure. Every successful acknowledgment must have an explicit meaning: accepted intent, committed state or completed external work. These are separate milestones.
Capacity and bottleneck reasoning
Assume one million tasks with 100,000 reminders/day: 1.16/s average; allow 100/s bursts. A due_at index supports a small polling worker. State a target of 99% of accepted reminders handed to the provider within 60 seconds under normal capacity.
Keep average and peak demand separate. Add replication, indexes, retries and safety headroom to raw arithmetic before selecting capacity. Load-test the busiest ownership key and recovery traffic as well as uniform happy-path throughput.
APIs and data model
- POST /tasks {title,dueAt,timeZone,requestKey} -> task
- PATCH /tasks/{id} {version,dueAt,completed} -> task or 409
- GET /tasks?cursor=... -> owner-scoped page
task(task_id PK, owner_id, title, completed_at, due_at, version)
reminder(reminder_id PK, task_id, task_version, due_at, state, lease_until)
delivery(operation_key UNIQUE, reminder_id, provider_status)Authorize resource ownership at the API boundary; do not trust IDs or state supplied by a client. Bind retry identity to the caller and payload for mutating operations. Reject an incompatible replay instead of returning an unrelated stored result.
Component relationships
Read the diagram
Follow the labeled relationship panels; they are separate directed paths, not one global timeline.
Step-by-step design
1. Establish the authority
Persist task and reminder intent in one transaction. Index active reminders by due_at. Workers claim bounded batches using leases and recheck task version and completion immediately before creating delivery intent.
2. Make retries and concurrent work safe
Editing increments task version and replaces reminder intent. A queued old version becomes a no-op; deleting a task must leave enough tombstone state to reject stale queue messages.
3. Scale and recover the critical path
Use stable delivery keys and track provider uncertainty. Client-side timers may improve UX but are not the durable scheduler. Store UTC instants and the original timezone for user-facing recurrence decisions.
Worked request and concurrency example
Task T has version 3 due at 09:00. At 08:59 the owner moves it to 10:00, creating version 4. A worker already holding version 3 checks the authoritative task and skips its send. At 10:00 it sends the version-4 reminder using key T:4. If the provider accepts but the response disappears, the worker reconciles or retries with provider-supported idempotency instead of silently creating a new delivery.
Failure and recovery walkthrough
Completing a task after a provider has accepted its reminder cannot recall an already sent email. Define cancellation as best effort after the dispatch boundary and display that limitation. A worker crash before dispatch is recovered after its lease expires.
Read the diagram
This numbered trace follows one scenario; explain the durable evidence at each transition.
Deep dive: task state and delivery state have different lifetimes
For this original baseline, one owner creates tasks with optional due reminders. A reminder may be queued, suppressed, dispatch-authorized, externally unknown or accepted by a provider. Completing a task does not erase history or retract a provider effect that already happened. Start with task/reminder rows, a due index and bounded workers; a client browser timer is only a convenience.
task(task_id, owner_id, due_at, version, completed_at, deleted_at)
reminder(reminder_id, task_id, task_version, nominal_due_utc, state)
delivery(delivery_id, reminder_id, operation_key, provider_state, evidence)
UNIQUE(task_id, task_version, nominal_due_utc, channel) for reminder occurrenceFor a one-shot task, a version change creates a replacement reminder and makes older queued versions ineligible. If recurrence is added, include each nominal UTC occurrence rather than treating every daily send as the same version-only effect. Preserve cancellation/deletion evidence for the full retry and replay horizon.
Make edits replace intent rather than mutate queued messages
At 08:59, T/v3 is due at 09:00. Its owner changes the due time to 10:00. One transaction updates T to v4 and stores the new reminder/publication intent. Old queue messages remain but their task_version no longer matches. A worker rechecks owner/resource policy, task completion/deletion and current version at the authoritative dispatch decision.
Read the diagram
Task T version 3 is queued for 09:00 → Owner commits version 4 due at 10:00 → A worker claims the old version 3 reminder → Authoritative eligibility rejects version 3 → Version 4 remains eligible for its later due time
If the user completes or deletes the task before dispatch authorization, suppress the unstarted reminder. If authorization wins first, a later completion may race with an already submitted effect. Define that boundary in the product. A best-effort last check before the network call can reduce obsolete sends, but it cannot atomically coordinate task edits with an arbitrary email provider.
Persist delivery intent before the uncertain call
| Boundary | Durable fact | Recovery |
|---|---|---|
| Task/edit transaction commits; queue send fails | Current reminder and outbox intent exist | Republish the same occurrence identity |
| Worker crashes before dispatch authorization | No provider call authorized by that attempt | Reclaim bounded work and recheck eligibility |
| Dispatch authorized; provider result lost | Original provider key and unknown delivery exist | Query/retry the same key under provider contract |
| Completion wins before dispatch claim | Task terminal/current version makes old reminder ineligible | Suppress it without a provider call |
| Completion happens after provider acceptance | Message may already be outside the service | Record best-effort cancellation boundary; do not promise recall |
A new worker must resolve an authorized-but-unknown delivery rather than abandon it simply because the task changed later. The stored provider identity is needed for reconciliation and duplicate prevention. If the provider cannot deduplicate or expose status, document the remaining uncertainty and retry policy rather than claiming exactly-once delivery.
Use time semantics the owner can understand
For a one-time reminder, store the chosen UTC instant plus original display timezone as needed. For “09:00 every local day,” store a named timezone and gap/fold policy and materialize eligible occurrences. Editing the timezone can affect future occurrences; define its effective version and the fate of already authorized work. Reuse the Job Scheduler rules rather than inventing a separate DST interpretation.
Due is a target, not proof of exact start time. With a two-second scan interval, one second queued and 500 ms worker/provider work, an eligible reminder can already be several seconds late under these exercise assumptions. A five-second objective needs a stage budget, adequate capacity and an overload/misfire policy; changing polling alone cannot guarantee it.
For one million reminders uniformly due over an hour, average due rate is about 278/s. If 100,000 are due at 09:00 and safe dispatch is 1,000/s, the burst needs at least 100 seconds to submit even with no other work. Rate limits, retry pressure and provider concurrency determine the real bound. Protect ordinary task reads from bulk catch-up work.
Keep ownership and diagnostics scoped
Knowing a task ID does not authorize an edit, completion or reminder-status read. Workers use scoped job evidence and current policy. Avoid placing task titles, addresses or signed links in routine telemetry. Expose lateness, suppression reason, unknown-provider age, task-version conflicts and oldest due work using bounded metrics dimensions.
Keep soft-deletion or another durable cancellation/version record until obsolete queue messages can no longer revive work. Physical cleanup must cover derived delivery/contact data under the retention policy without removing dedupe evidence prematurely. Account deletion is not implemented by deleting one task row and hoping all providers forget it.
Reminder assessment: find the irreversible boundary
Question: T/v3 was queued before the owner moved T to v4. May v3 send because it obtained a worker lease first?
Show answer and explanation
Answer: No. A lease is scheduling ownership, not current task eligibility. The authoritative task/version/terminal-state check must reject the old unstarted occurrence.
Question: The task was completed after the provider accepted the reminder. Can the UI promise the email was recalled?
Show answer and explanation
Answer: Not from a local task update. Record the actual cancellation boundary and provider evidence; external acceptance may already make recall unavailable.
Question: A due time is represented by a local daily 09:00 rule. Is adding 24 elapsed hours always equivalent to the next local day?
Show answer and explanation
Answer: No. DST and rule changes can alter eligible UTC instants. Preserve the timezone and declared calendar policy in versioned occurrence generation.
Score one point for versioned reminder identity, edit/dispatch arbitration, external uncertainty recovery, calendar semantics and bounded lateness/tenant access. Then change one-shot reminders to recurring reminders and explain why occurrence identity and dedupe lifetime must change.
See Idempotency and the transactional outbox pattern for the publication/duplicate boundaries. The task histories and timing figures are original exercises, not provider latency guarantees.
Decisions and alternatives
| Decision | Trade-off |
|---|---|
| Alternative 1 | Database polling is simple at this workload; time buckets and a dedicated scheduler are justified when due scans or burst dispatch become bottlenecks. |
| Alternative 2 | At-least-once work with deduplicated delivery is robust. Claiming exactly-once email without provider support hides an unavoidable uncertain-outcome window. |
Operational contract
Track due-to-claim and due-to-provider latency separately, lease expiry, stale-version skips and oldest pending delivery. Protect owner-scoped reads and avoid logging task text in operational events.
Choose alert thresholds from the agreed user-facing objective. Assign an owner for stuck work and a replay/repair procedure that preserves the original business identity. Bound queue depth, concurrency and recovery throughput so an outage does not produce an uncontrolled catch-up burst.
Foundation, senior and staff depth
- Foundation: Define versioned task and reminder occurrence identities.
- Senior: Arbitrate edits/completion against dispatch authorization and reconcile unknown deliveries.
- Staff: Budget bursts, calendar semantics and dedupe retention under provider limits.
Practice before revealing the answer
Spend 45 minutes: 5 clarifying scope, 7 on capacity/APIs/state, 12 on the first architecture, 15 on the concurrency and failure examples, and 6 on trade-offs and operations. Draw your own design before comparing it with this chapter.
Question: Will increasing polling frequency guarantee zero reminder delay?
Show answer and explanation
Answer: No. Polling is only one delay source; queueing, clock error and provider throttling also matter. Budget each stage, bound concurrency and expose lateness percentiles.
Verification and rollout
Enumerate edit/completion before and after dispatch authorization. Replay queue messages and resolve lost provider responses with the original operation key. Verify UTC occurrence identities, cancellation tombstones and backlog arithmetic.
Primary technical reference
Official documentation for the mechanism used in this exercise. This lesson is an original interview design, not a description of undisclosed company internals.
Related study
Before this design, review these mechanisms and identify which owns the critical state transition:
Use the Interview Playbook to adapt the depth and timing, then return to the question breakdown overview.
Will increasing polling frequency guarantee zero reminder delay?
Your design draft
Clarify assumptions, explain your approach, and test the difficult cases. Save your draft, then compare it with the study notes.
Self-review checklist
Self-guided practice. Automated AI feedback and code execution are not connected.