Calendar & Scheduling System Design: Events, Recurrence, and Time Zones at Scale
Calendar & Scheduling System Design#
A calendar looks simple on the surface — just boxes on a grid. Under the hood, it is one of the most deceptively complex systems to build correctly. Time zones, recurring events, and multi-party scheduling create a web of edge cases that trip up even experienced engineers.
Core Requirements#
Functional:
- Create, read, update, delete events (CRUD)
- Recurring events with flexible patterns
- Time zone-aware storage and display
- Invitations and RSVP tracking
- Free/busy availability queries
- Reminders and notifications
- Shared calendars and delegation
- Sync with external calendars (CalDAV/iCalendar)
Non-functional:
- Read-heavy workload (users check calendars far more than they create events)
- Low latency for calendar views (under 200ms)
- Strong consistency for conflict detection
- Reliable reminder delivery (never miss a reminder)
Data Model#
Events#
Event {
id: UUID
calendar_id: UUID
title: string
description: text
start_time: timestamp with time zone
end_time: timestamp with time zone
timezone: string (IANA, e.g. "America/New_York")
location: string
recurrence_rule: string (RFC 5545 RRULE, nullable)
created_by: UUID
status: CONFIRMED | TENTATIVE | CANCELLED
visibility: PUBLIC | PRIVATE | CONFIDENTIAL
created_at: timestamp
updated_at: timestamp
}
Key Design Decisions#
- Store times in UTC with the original IANA time zone — this handles daylight saving transitions correctly
- Never store only an offset like +05:00. Offsets change with DST; time zones do not
- Separate
recurrence_rulefrom the event itself — a single event row represents the "template," and occurrences are expanded on read
Recurring Events (RFC 5545)#
Recurring events are the hardest part of calendar design. The iCalendar standard (RFC 5545) defines the RRULE format:
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20261231T235959Z
→ Every Monday, Wednesday, Friday until end of 2026
RRULE:FREQ=MONTHLY;BYMONTHDAY=15;COUNT=12
→ 15th of every month, 12 occurrences
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
→ Second Sunday of March every year (DST start in the US)
Expansion Strategy#
Two approaches to generating occurrences:
Option A — Expand on read (virtual instances):
- Store only the RRULE on the master event
- When a user views a date range, compute which occurrences fall in that window
- Pros: minimal storage, easy to update the whole series
- Cons: expansion logic is complex, harder to query
Option B — Materialize occurrences:
- Pre-generate individual event rows for each occurrence
- Pros: simple queries, easy per-occurrence edits
- Cons: storage-heavy, updating the series requires updating many rows
Hybrid approach (recommended):
- Store the RRULE on the master event
- Expand on read for display
- Materialize only exceptions — instances where the user edited a single occurrence (changed time, added a note, declined one instance)
Exception storage:
RecurrenceException {
master_event_id: UUID
original_start: timestamp -- identifies which occurrence
modified_event_id: UUID -- points to the override event
is_cancelled: boolean -- true if this occurrence was deleted
}
Time Zone Handling#
Time zones are a minefield. Key rules:
- All-day events have no time zone — "March 15" means March 15 in the viewer's local zone
- Timed events store UTC + IANA zone — "3 PM America/Chicago" converts to UTC for storage but preserves the original zone for display
- DST transitions — a weekly 2:30 AM meeting on the day clocks spring forward? It does not exist. The system must handle this gracefully (skip or shift)
- Floating time — some events mean "9 AM wherever I am" (e.g., morning routine). Store these without a zone and resolve at display time
Use the IANA Time Zone Database (tzdata) and keep it updated. Political time zone changes happen multiple times per year.
Availability and Free/Busy Queries#
Free/busy is critical for scheduling:
GET /api/users/{id}/freebusy?start=2026-03-28T00:00:00Z&end=2026-03-29T00:00:00Z
Response:
{
"busy": [
{"start": "2026-03-28T14:00:00Z", "end": "2026-03-28T15:00:00Z"},
{"start": "2026-03-28T17:00:00Z", "end": "2026-03-28T18:00:00Z"}
]
}
Implementation:
- Query all events in the time range (including expanded recurrences)
- Strip private details — only return busy/free intervals
- Cache aggressively — free/busy rarely changes minute-to-minute
- For multi-user scheduling ("find a time that works for everyone"), intersect free/busy across participants
Scheduling Conflicts#
When creating an event with invitees:
- Query each invitee's free/busy for the proposed time
- Flag conflicts but do not block — users may intentionally double-book
- Suggest alternative times by scanning overlapping free windows
- Rank suggestions by minimal disruption (closest to original time, fewest moves)
Invitations and RSVP#
The invitation workflow:
1. Organizer creates event with attendees
2. System sends invitations (email + in-app notification)
3. Each attendee responds: ACCEPTED | DECLINED | TENTATIVE
4. Organizer sees aggregated responses
5. Changes to the event trigger update notifications
6. Cancellation sends cancellation notices
Data model addition:
EventAttendee {
event_id: UUID
user_id: UUID
email: string
status: NEEDS_ACTION | ACCEPTED | DECLINED | TENTATIVE
role: ORGANIZER | REQUIRED | OPTIONAL
responded_at: timestamp
}
For external attendees (no account), send iCalendar .ics attachments via email — this is the universal standard that Outlook, Apple Calendar, and Google Calendar all understand.
Reminders and Notifications#
Reminders must be reliable — a missed meeting reminder is a serious failure.
Architecture:
┌──────────┐ ┌───────────────┐ ┌──────────────┐
│ Event DB │─────►│ Reminder │─────►│ Notification │
│ │ │ Scheduler │ │ Service │
└──────────┘ │ (cron / queue)│ │ (push/email) │
└───────────────┘ └──────────────┘
- When an event is created or updated, enqueue a reminder job at
event_start - reminder_offset - Use a persistent job queue (not in-memory cron) — SQS, Redis Sorted Sets, or a database-backed scheduler
- Support multiple reminders per event (30 min before, 10 min before)
- Delivery channels: push notification, email, SMS
- Idempotency: if the reminder worker crashes and restarts, it should not send duplicates
External Sync (CalDAV)#
CalDAV is the open protocol for calendar sync (used by Apple Calendar, Thunderbird, and many others).
Key concepts:
- Each calendar is a collection on the server
- Events are stored as iCalendar (
.ics) resources - Clients use
REPORTrequests to fetch changes since last sync - CTag (collection tag) changes when any event in the calendar changes — clients poll this to detect updates
- ETag per event enables conditional updates (optimistic concurrency)
For Google Calendar integration, use the Google Calendar API (REST-based, not CalDAV).
Sync conflict resolution:
- Server wins for concurrent edits from different clients
- Preserve both versions and let the user resolve if the conflict is significant
- Use ETags to detect stale writes
Shared Calendars#
Shared calendars add a permission layer:
- Owner — full control, manage sharing
- Editor — create and modify events
- Viewer — read-only access
- Free/busy only — can see busy times but not event details
Implementation:
CalendarShare {
calendar_id: UUID
shared_with: UUID (user or group)
permission: OWNER | EDITOR | VIEWER | FREEBUSY
granted_by: UUID
granted_at: timestamp
}
Organization calendars (company holidays, team schedules) are shared calendars owned by an admin with viewer access granted to the whole organization.
Scaling Considerations#
- Read-heavy pattern — cache calendar views aggressively. Invalidate on event changes
- Partition by user — each user's calendars and events are a natural shard key
- Recurring event expansion is CPU-intensive — cache expanded ranges
- Reminder scheduling — millions of reminders firing at popular times (9 AM Monday). Use distributed workers with rate limiting
- Search — full-text search over event titles and descriptions via Elasticsearch or similar
- Storage estimates — average user has around 500 events/year. At 1 KB per event, 10 million users need approximately 5 TB/year before recurrence expansion
Technology Choices#
| Component | Options |
|---|---|
| Database | PostgreSQL (events + ACL), Redis (caching, free/busy) |
| Queue | SQS, Redis Sorted Sets, or Celery for reminders |
| Sync protocol | CalDAV for open clients, REST API for web/mobile |
| Time zone data | IANA tzdata (keep updated quarterly) |
| Notifications | Firebase Cloud Messaging, APNs, SendGrid |
| Search | Elasticsearch for event search |
Key Takeaways#
- Store UTC + IANA time zone — never just an offset
- Recurring events are the hardest part — use hybrid expansion with materialized exceptions
- Free/busy is a first-class API — essential for multi-party scheduling
- Reminders need a persistent job queue — in-memory scheduling loses jobs on crash
- CalDAV and iCalendar are the universal interchange formats
- Partition by user for natural horizontal scaling
Ready to practice calendar system design and dozens of other real-world architectures? codelit.io gives you interactive tools to master system design — from calendar scheduling to collaborative editing.
This is article #212 in the Codelit engineering blog series.
Try it on Codelit
Chaos Mode
Simulate node failures and watch cascading impact across your architecture
Related articles
AI Agent Tool Use Architecture: Function Calling, ReAct Loops & Structured Outputs
6 min read
AI searchAI-Powered Search Architecture: Semantic Search, Hybrid Search, and RAG
8 min read
AI safetyAI Safety Guardrails Architecture: Input Validation, Output Filtering, and Human-in-the-Loop
8 min read
Try these templates
Uber Real-Time Location System
Handles 5M+ GPS pings per second using H3 hexagonal geospatial indexing.
6 componentsReal-Time Collaborative Editor
Notion-like document editor with real-time collaboration, conflict resolution, and rich media.
9 componentsNetflix Video Streaming Architecture
Global video streaming platform with adaptive bitrate, CDN distribution, and recommendation engine.
10 componentsBuild this architecture
Generate an interactive architecture for Calendar & Scheduling System Design in seconds.
Try it in Codelit →
Comments