Skip to content

Calendar / Reminder Service#

Problem statement (interviewer prompt)

Design Google Calendar: events with attendees, RSVP, recurring rules (RFC 5545 RRULE), time-zone correctness, sharing, free/busy queries, and reminder delivery (push + email) at the right local time for each attendee.

flowchart LR
  U([User])
  CAL[Calendar Service]
  REM([Reminder Scheduler])
  Q[(Delayed queue)]
  NOTIF[Notification Service]
  U --> CAL --> REM --> Q --> NOTIF --> U

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class U client;
    class CAL,NOTIF service;
    class Q datastore;
    class REM compute;
flowchart TB
  subgraph Apps
    APP[Calendar app]
  end

  subgraph Edge
    GW
  end

  subgraph Cal[Calendar]
    CAL[Calendar Service]
    EVT[(Events DB)]
    RECUR[Recurrence expander - RFC 5545 RRULE]
    INV[Invitees + RSVPs]
    FREEBUSY[Free/busy lookup]
    OOO[Out-of-office]
  end

  subgraph Sched[Reminder scheduler]
    DELAYQ[[Delayed queue<br/>per-time bucket]]
    WHEEL[Hierarchical timing wheel]
    LEASE[Per-bucket lease]
    DEDUP[Idempotency keys]
  end

  subgraph Notif
    NS[Notification Service]
    PUSH((APNS / FCM))
    EMAIL
    SMS
  end

  subgraph Sync
    CALDAV[CalDAV / Google Calendar API]
    PUSH_CHN[Realtime change channel]
  end

  Apps --> Edge --> Cal
  Cal --> Sched --> Notif
  Sync --- Cal

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class APP,CAL,RECUR,INV,FREEBUSY,OOO,WHEEL,DEDUP,NS,CALDAV,PUSH_CHN service;
    class EVT datastore;
    class DELAYQ queue;
    class LEASE storage;
    class PUSH external;

Recurrence#

  • RFC 5545 RRULEs (e.g., FREQ=WEEKLY;BYDAY=MO,WE).
  • Expand on read or schedule per-occurrence ahead.
  • Exceptions (deletes / modifications) stored separately.

Scheduler at scale#

  • Time-bucketed queue: per-minute buckets.
  • Workers lease a bucket at minute boundary; dispatch.
  • For high-volume reminders, timing wheel with O(1) ops.

Glossary & fundamentals#

Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.

Tag Concept What it is Page
HLD Idempotency & retries safe re-execution, backoff + jitter idempotency-retries
LLD REST API design verbs, statuses, pagination, errors rest-api-design
LLD Async models futures / async-await / coroutines / actors async-models
LLD Error handling exceptions vs Result, error boundaries error-handling

Quick reference#

Functional#

  • Create events with start, end, attendees.
  • Recurrence (RRULE).
  • Reminders via push/email/SMS.
  • Free/busy queries.
  • Sync via CalDAV / Google API.

Non-functional#

  • Sub-second reminder fire accuracy at minute granularity.
  • 99.95% uptime.

Trade-offs#

  • Expand recurrence on read vs materialize occurrences: hybrid - materialize next N occurrences for fast fan-out.
  • Time-bucket queue vs timing wheel: wheel scales to billions of pending timers in memory.

Refs#

  • RFC 5545 (iCalendar), RFC 4791 (CaldDAV).
  • Google Calendar engineering posts.
  • "Hashed and Hierarchical Timing Wheels" Varghese & Lauck.

FAQ#

How do you store recurring calendar events?#

Store the master event with an RFC 5545 RRULE (FREQ, INTERVAL, BYDAY) plus exception entries for individually edited instances. Materialize occurrences on read inside the requested window.

How does the reminder scheduler work?#

On event create or update, enqueue a delayed message with a deliver-at timestamp. A delayed queue or timer wheel releases it at the right moment to the notification service.

How do you handle time zones for reminders?#

Store the event in its native time zone and the user's preferred zone on their account. The scheduler converts at fire time so DST transitions resolve correctly.

How do you answer free/busy queries fast?#

Maintain a per-user busy-block index over a 90-day window, refreshed when events change. Free/busy is then a small range scan instead of expanding every recurring rule.

How do you scale to millions of calendars?#

Shard event storage by calendar_id so reads stay local. Use a separate fan-out service to materialize attendee notifications, since one event can fire reminders to thousands.

Video walkthrough

System Design Interview: Design Calendar Application : via Exponent