System Design : Calendar App
Source: Dev.to
Functional Requirements
- Create event, modify event, cancel event
- View calendar daily, weekly, or yearly
- Set up recurring meetings
- Send notification for any change via email
Non‑Functional Requirements
- High availability → consistency (eventual consistency for syncing events)
- Should support 1 billion users
- Low latency for viewing the calendar (read‑heavy > write‑heavy)
Data Models
- User
- Event
- Recurrence
APIs
POST /events/
{
"title": "Meeting title",
"userId": "creator_id", // creator
"userIds": ["participant1", "participant2"], // optional list of attendees
"startTime": "2025-01-10T15:00:00Z",
"endTime": "2025-01-10T16:00:00Z",
"content": { // JSON blob: video call link, description, etc.
"videoCallLink": "https://example.com/meet/123",
"description": "Discuss project status"
},
"recurrence": "weekly|biweekly|monthly|yearly" // optional
}
GET /events?startDay=&endDay=
Returns a list of events that fall within the requested date range.
High Level Design
(Overview of components, caching layer, database, and client synchronization.)
Deep Dives
1. Storing Daily or Recurring Events
Event Creation & Storage
Case 1 – Simple (One‑Time) Event
Example: Doctor Appointment
| event_id | owner_id | title | start_time_utc | end_time_utc | rrule | tz | version |
|---|---|---|---|---|---|---|---|
| evt_101 | user_1 | Doctor Appointment | 2025‑01‑10 15:00 | 2025‑01‑10 16:00 | NULL | UTC | 1 |
The row is returned and rendered directly; no expansion needed.
Case 2 – Recurring Event (Weekly)
Example: Team Sync
| event_id | owner_id | title | start_time_utc | end_time_utc | rrule | tz | version |
|---|---|---|---|---|---|---|---|
| evt_201 | user_1 | Team Sync | 2025‑01‑06 10:00 | 2025‑01‑06 11:00 | FREQ=WEEKLY;BYDAY=MO | UTC | 1 |
GET behavior (Week view) – fetch the base row, expand the rule only for the requested range, generate occurrences in memory.
Case 3 – Recurring Event with Exception (One Cancellation)
Example: Team Sync on Jan 20 cancelled
event_exceptions table:
| exception_id | event_id | exception_date | type |
|---|---|---|---|
| ex_301 | evt_201 | 2025‑01‑20 | CANCELLED |
GET behavior (Week of Jan 20) – expand the weekly rule, match the exception, drop that occurrence, and return the remaining events.
Storage Summary
- One‑time event → 1 row
- Weekly recurring for years → 1 row
- Weekly + N exceptions → 1 + N rows
Result: minimal storage.
2. View Generation & Low Latency
User flow: Server sends a list of events to the client.
Optimization – Redis Cache
- Client requests events.
- Server checks Redis:
- Cache hit → return cached events.
- Cache miss → query the DB, populate Redis, return result.
Pros: fast reads, reduced DB load.
Cons: cache invalidation complexity, eventual consistency window.
Conclusion: Hybrid approach – client‑side expansion + SQLite for offline storage gives low latency and offline capability.
3. Conflict Detection & Locking Strategy
- Optimistic Locking (default) – low contention, suitable for personal calendars; requires retry logic in the service.
- Pessimistic Locking – for high‑contention calendars; locks multiple rows to avoid race conditions.
4. Multi‑Device Sync (Push & Pull)
- Pull (Delta Sync) – used for cold start, reconnect, missed updates.
- Push (SSE / Push Notifications) – fetch updated rule, re‑expand locally, update SQLite + UI. SSE is preferred: one‑way, lightweight, battery‑friendly.
- Hybrid model – push for freshness, pull for correctness.
5. Database Choice: SQL vs NoSQL
Why SQL works well
- Write volume is modest; relational databases scale adequately.
- Transactions simplify conflict checks.
- Recurrence queries (RRULE expansion) fit relational models.
- Strong consistency (or configurable eventual) is easier to guarantee.
NoSQL trade‑offs
- Conflict checks are hard without transactions.
- Recurrence queries are a poor fit.
- Consistency is eventual by default, adding complexity for correctness.
- Higher implementation complexity for the same guarantees.