0) Problem Restatement
Design a calendar service like Google Calendar. Users create events, invite others, and see their schedule in day, week and month views. Events can repeat ("every Monday at 10 AM"), attendees can accept or decline, and users get reminders before events start. People live in different time zones, and they use the calendar on several devices that must stay in sync. A common variant asks for a meeting scheduler that finds free times and prevents double-booking of rooms.
Asked at: Flipkart, Google, LinkedIn, OpenAI, Snowflake, Uber — 8 candidate reports between Nov 2025 and Aug 2026.1) Requirements
1.1 Functional
- Create, edit and delete events (one-time and recurring).
- Invite attendees. They receive invites and can respond (accept, decline, maybe).
- View a date range (a week, a month) quickly.
- Detect conflicts and suggest free slots for a group.
- Reminders (e.g., 10 minutes before).
- Share calendars (view or edit access).
1.2 Non-Functional
- Read-heavy: people open the calendar far more often than they change it.
- Correct times across time zones and daylight saving changes.
- Sync across devices within a few seconds.
- Reliable reminders: they should fire on time and only once.
1.3 Scale Estimates
- 500 million users, 100M daily active.
- Each user creates ~2 events/day → 200M events/day ≈ 2,300 writes/sec.
- Views: 10 opens per user per day → 1B range queries/day ≈ 12K/sec.
- Storage: ~1 KB per event → 200 GB/day before replication.
1.4 API Design
POST /v1/eventswith{ title, start, end, timezone, rrule?, attendees[], reminders[] }GET /v1/calendars/{id}/events?from=2026-09-21&to=2026-09-28PATCH /v1/events/{id}?scope=this|following|all(edit one occurrence of a recurring event, or all of them)POST /v1/events/{id}/respondwith{ status: accepted }POST /v1/freebusywith{ users[], from, to }→ busy blocks for each user
2) High-Level Architecture
2.1 Overview
- Event Service: create, edit, delete and view events.
- Event DB: stores events and attendees (sharded by calendar ID).
- Invite Service: when an event has attendees, it adds the event to each attendee's calendar and sends emails or notifications.
- Free/Busy Service: answers "when are these people busy?" from a compact busy-time index.
- Reminder Scheduler: fires reminders at the right time (a delayed job queue).
- Sync Service: pushes changes to the user's other devices.
2.2 Architecture Diagram
Architecture Diagram
flowchart LR
C["Web / Mobile clients"] --> API["API Gateway"]
API --> ES["Event Service"]
ES --> DB[("Event DB - sharded by calendar")]
ES --> K[("Change events - Kafka")]
K --> INV["Invite Service"]
K --> FB["Free/Busy indexer"]
K --> REM["Reminder Scheduler"]
K --> SYNC["Sync / Push Service"]
INV --> DB
FB --> FBS[("Busy-time index")]
REM --> N["Notifications"]
SYNC --> C3) Data Model
CREATE TABLE events (
event_id UUID PRIMARY KEY,
calendar_id UUID, -- owner calendar
title TEXT,
start_utc TIMESTAMP, -- first occurrence start, stored in UTC
end_utc TIMESTAMP,
timezone TEXT, -- e.g. "America/New_York", needed for recurrence
rrule TEXT, -- e.g. "FREQ=WEEKLY;BYDAY=MO", NULL if one-time
recur_until TIMESTAMP, -- last possible occurrence (or far future)
version INT
);
CREATE TABLE event_exceptions ( -- one changed or cancelled occurrence
event_id UUID, original_start_utc TIMESTAMP, new_start_utc TIMESTAMP,
new_end_utc TIMESTAMP, cancelled BOOLEAN
);
CREATE TABLE attendees (
event_id UUID, user_id UUID, response TEXT, -- accepted, declined, tentative
PRIMARY KEY (event_id, user_id)
);
CREATE INDEX ON events (calendar_id, start_utc);
4) Recurring Events (the key idea)
We do not create one row per occurrence. "Every Monday forever" would be infinite. Instead:
- Store the rule (an RRULE, the standard iCalendar format) plus the time zone.
- When someone views a week, load events whose range overlaps the week (
start_utc <= week_end AND recur_until >= week_start), then expand each rule into concrete times for that week only. - Apply exceptions: "this Monday moved to Tuesday" or "cancelled on Dec 25".
- Editing "this and following" splits the series: end the old rule at that date and create a new rule starting from it.
5) Key Flows
5.1 Creating a meeting with attendees
- Save the event in the organizer's calendar.
- Publish a change event. The Invite Service links the event into each attendee's calendar (a row pointing to the same
event_id) and sends invites. - The Free/Busy indexer updates each attendee's busy blocks.
- The Reminder Scheduler schedules reminders for the next occurrence.
5.2 Finding a free slot
- Get busy blocks for all attendees in the range from the Free/Busy index. It stores only start/end pairs, with no titles, which also protects privacy.
- Merge all busy intervals (sort by start and combine overlaps), then return the gaps that are long enough and fall inside working hours.
6) Deep Dive — Reminders, rooms and sync
- Reminders: store the next reminder time per event and user in a delayed queue (a Redis sorted set by time, or a job scheduler). A worker fires due reminders. For recurring events, it then schedules the next occurrence's reminder. Use an idempotency key
(event, occurrence, user)so a reminder never fires twice. - Room booking without double booking: treat each room as a calendar. Booking uses a transaction that checks for overlapping bookings of that room and inserts in the same step, e.g., a database exclusion constraint on the time range, or a lock on the room.
- Multi-device sync: every change bumps a per-calendar sync token (a sequence number). Devices ask "what changed since token 5812?" and get only the differences. A push notification tells devices to sync right away.
- Huge meetings (all-company events with 50K attendees): don't copy the event into 50K calendars. Store it once and have attendees reference it, then send invites asynchronously in batches.
7) Trade-offs & Alternatives
| Decision | Choice | Why | Alternative |
|---|---|---|---|
| Recurring events | Store rule + expand on read | Small storage, easy series edits | Store every occurrence: simple queries, huge and hard to edit |
| Time storage | UTC + original time zone | Correct across DST | Local time only: breaks for travelers and DST |
| Free/busy | Separate busy-time index | Fast group queries, private | Scan full events: slower, leaks details |
| Device sync | Sync tokens + push | Only sends changes | Full reload: simple but wasteful |
8) Common Follow-up Questions
- "How do you show a month view fast?" Cache expanded occurrences per calendar per month, and invalidate when an event in that calendar changes.
- "External invites (other providers)?" Send standard iCalendar (.ics) emails and accept replies by email.
- "Privacy?" Shared calendars can be "free/busy only", "see details" or "edit". Check this on every read.
9) Wrap-Up
Store events in UTC with their time zone, keep recurring events as rules with exceptions, and expand them only for the range being viewed. Use change events to drive invites, a free/busy index, reminders and device sync. Prevent room double-booking with a transactional overlap check, and keep sync efficient with sync tokens.