DDSA Solutions
Case Study6 min read·

Design Google Calendar

How to design Google Calendar for interviews: events and recurrence, availability, invites, reminders, sync, and concurrency on overlapping edits.

Calendar looks like CRUD until recurrence, time zones, and “find a slot for five people” show up. It borrows availability thinking from ticket booking (busy intervals, not finite seats) and delivery patterns from notifications (reminders). Interviewers listen for event modeling and sync — not whether you memorized iCal RFCs.

Scope with the framework: personal calendars, create/edit events, invites, reminders, basic free/busy. Full Google Meet orchestration can point at Zoom as a dependency.

Functional requirements

  • Create, update, delete events with start/end, timezone, attendees.
  • Support recurring events (daily/weekly/monthly) with exceptions.
  • Invite users; track accept/decline/tentative.
  • Reminders via push/email/SMS.
  • Sync across web and mobile; offline edits with conflict resolution.
  • Optional: shared calendars, free/busy scheduling assistant.

Non-functional

  • Read-heavy: month views dominate writes.
  • Strong-ish consistency for a user’s own primary calendar; eventual OK for free/busy of others.
  • Correct time-zone arithmetic — DST bugs are legendary.
  • Reminder delivery within a small skew (tens of seconds).

Store rules, expand on read

Do not insert 10 years of daily rows for a recurrence. Persist an RRULE (or equivalent) plus exception list; expand instances for the visible window (e.g. ± a few months). That single decision keeps storage sane.

Capacity sketch

1B users is fantasy scale for a whiteboard — pick 100M MAU, 5 events created/user/week, huge read amplification from month grids. Reminder fan-out near the top of the hour causes spikes — classic job scheduler load shape.

High-level architecture

  1. API / sync service — CRUD + incremental sync tokens (API design).
  2. Calendar/event store — SQL sharded by calendar_id / user_id.
  3. Recurrence expander — library used by read path and reminder planner.
  4. Invite service — attendee rows + email notifications.
  5. Reminder scheduler — materialize near-term firings into a queue.
  6. Notification workers — push/email/SMS.
  7. Free/busy index — denormalized busy intervals for scheduling queries.
  8. Client sync — local DB + conflict policy.

Data model

EntityFieldsNotes
Calendarowner, acl, tz defaultShard key
Eventstart, end, tz, title, rrule, series_idMaster for recurrence
Exceptioninstance_start, overrides / cancelledThis-one vs all-following
Attendeeevent_id, user_id, statusRSVP state
Reminderevent_id, fire_at, channelOr derived from offsets

Recurrence and edits

Editing “this event” writes an exception. Editing “this and following” splits the series (end old RRULE, start new series). Editing “all” updates the master. Expand with a well-tested library; do not hand-roll monthly edge cases live. For reads, query masters overlapping the window, expand, subtract exceptions.

Invites and free/busy

Creating an event with attendees inserts attendee rows and sends notifications. Free/busy for scheduling: maintain busy intervals per user (expanded a few months ahead by a worker). Query intersection for proposed slots — similar spirit to availability in Airbnb, but for people instead of listings.

Reminders

  1. On create/update, compute next fire times for the near horizon (e.g. 48 h).
  2. Persist due reminders; a dispatcher polls/queues due work (scheduler).
  3. Send push; mark delivered; schedule the following occurrence for long series.
  4. Idempotent delivery keys prevent double pings on worker retry.

Sync and conflicts

Clients hold a sync_token / version per calendar. Pull returns changed events since token. Concurrent edits: last-write-wins with updated_at + etag is MVP; optional OT/CRDT is overkill (point to Docs only if they insist on co-editing the same event description). Offline creates use client-generated UUIDs.

Scaling

  • Shard by user_id / calendar_id (sharding).
  • Cache month views in Redis (caching) with short TTL or version keys.
  • Reminder load: shard dispatchers by fire_at ranges; smooth top-of-hour spikes with jitter.
  • Read replicas for free/busy; primary for event writes.

Worked example

  1. Alice creates weekly standup RRULE with Bob; exceptions none.
  2. Bob’s client syncs new series; both get a 10-minute reminder job materialized for the next meeting.
  3. Alice edits only next Tuesday → exception row overrides that instance.
  4. Charlie checks Alice free/busy; busy index shows Tuesday blocked, other Tuesdays still series-busy.

Interview narrative

Lead with event + RRULE + exceptions, then sync tokens and reminder scheduler. Contrast with ticket booking (finite seats) and chat (ephemeral). Keep Meet/Zoom as an external join link. That is Google Calendar at interview depth.

More in this series