Calendar / Meeting Scheduler (LLD)#
Problem statement (interviewer prompt)
Design a calendar / meeting scheduler: users own calendars; events have time ranges, attendees, optional recurrence (daily/weekly). Support conflict detection and "find earliest 30-min slot for N attendees this week".
classDiagram
class User { +id, email, timezone }
class Calendar {
+owner : User
-events : List~Event~
+addEvent(e)
+eventsBetween(start, end) List~Event~
+isBusyAt(t) boolean
}
class Event {
+id, title, organizer : User
+start, end : Instant
+attendees : List~User~
+recurrence : RecurrenceRule
+overlaps(other) boolean
}
class RecurrenceRule {
<<abstract>>
+nextOccurrences(from, count) List~TimeRange~
}
class Daily { +intervalDays }
class Weekly { +daysOfWeek : Set~DayOfWeek~ }
class Monthly { +dayOfMonth }
class Scheduler {
-calendars : Map~User, Calendar~
+findSlot(attendees, durationMinutes, between) Optional~TimeRange~
}
Calendar *-- Event
User --> Calendar : owns
Event --> RecurrenceRule
RecurrenceRule <|-- Daily
RecurrenceRule <|-- Weekly
RecurrenceRule <|-- Monthly
Conflict detection: time-range overlap. Find-slot: union of all busy ranges, then scan free gaps for the first one ≥ duration.
A rich LLD: time ranges, recurrence, multi-participant queries, timezones, reminders.
Domain model#
classDiagram
class User { +id, email, timezone : ZoneId }
class Calendar {
+owner : User
-events : IntervalTree~Event~
+addEvent(e)
+remove(eventId)
+eventsBetween(start, end) List~Event~
+isBusyAt(t) boolean
}
class Event {
+id, title, organizer : User
+start, end : Instant
+attendees : List~User~
+recurrence : RecurrenceRule
+location, description
+overlaps(t1, t2) boolean
}
class RecurrenceRule {
<<abstract>>
+occurrencesBetween(from, to) List~TimeRange~
}
class Daily { +interval : int }
class Weekly { +daysOfWeek : Set~DayOfWeek~, +interval : int }
class Monthly { +dayOfMonth : int, +interval : int }
class TimeRange { +start, end : Instant }
class Scheduler {
-calendars : Map~User, Calendar~
+findEarliestSlot(attendees, duration, window) Optional~TimeRange~
+schedule(event)
}
class ReminderService {
+scheduleReminder(event, beforeMinutes)
}
Calendar *-- Event
User --> Calendar : owns
Event --> RecurrenceRule
RecurrenceRule <|-- Daily
RecurrenceRule <|-- Weekly
RecurrenceRule <|-- Monthly
Scheduler ..> Calendar
Scheduler ..> ReminderService
Conflict detection#
A naive linear scan is O(N). With many events, use an interval tree (or sorted list with binary search):
class IntervalTree:
def insert(self, ev): ...
def query(self, start, end) -> list: ... # O(log N + K)
For recurring events, expand occurrences within the query window.
Find earliest slot for N attendees#
def find_earliest(self, attendees, duration_min, window_start, window_end):
# Union all busy intervals across attendees
busy = []
for u in attendees:
for e in self.calendars[u].events_between(window_start, window_end):
busy.append((e.start, e.end))
busy.sort()
busy = merge_overlapping(busy)
# Walk gaps
prev_end = window_start
for s, e in busy:
if (s - prev_end).total_seconds() / 60 >= duration_min:
return TimeRange(prev_end, prev_end + duration_min*60)
prev_end = max(prev_end, e)
if (window_end - prev_end).total_seconds() / 60 >= duration_min:
return TimeRange(prev_end, prev_end + duration_min*60)
return None
O((N+K) log K) for K total events across attendees.
Recurrence#
class Weekly(RecurrenceRule):
def __init__(self, days_of_week, interval=1):
self.days = days_of_week
self.interval = interval
def occurrences_between(self, start, end):
result = []
d = start.date()
week_index = 0
while d <= end.date():
if (d - start.date()).days // 7 % self.interval == 0:
if d.dayofweek in self.days:
result.append(TimeRange(d.with_time(orig_start), d.with_time(orig_end)))
d += timedelta(days=1)
return result
For real apps, use RRULE (RFC 5545 iCal) parsers.
Timezones#
- Store events in UTC; convert on display by user's timezone.
- DST transitions: a "9 AM weekly" event at user's tz may shift by an hour twice a year. Recurrence rule operates in the user's tz, materialised to UTC.
Reminders#
flowchart LR
Event --> Sched[Scheduler enqueues reminder<br/>at event.start - 15min]
Sched --> Queue[(Delayed queue)]
Queue --> Worker[Worker sends notification]
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 Sched,Worker service;
class Queue queue;
Timer wheel or delayed queue (Redis ZSET, RabbitMQ TTL+DLX) for at-scale reminders.
Concurrency#
- Two organisers scheduling overlapping meetings: optimistic version on event; conflict check at commit.
- Calendar adds: per-user lock or row version.
Extensions#
- Booking links (Calendly-style available slots).
- Room reservations as resources.
- Shared calendars with ACLs.
- Cancellations + rescheduling (event versioning).
Where calendar LLD fits#
flowchart TB
CAL((Calendar<br/>scheduler LLD))
CRH["Calendar reminder (HLD)<br/>system-design sibling"]
SM[State machines<br/>event lifecycle]
BPL[Behavioral patterns<br/>Strategy for recurrence]
POL[Pessimistic / optimistic locking<br/>conflict detection]
CAL -. complement .- CRH
SM --> CAL
BPL --> CAL
POL --> 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 CAL service;
class CRH,SM,BPL,POL datastore;
Glossary & fundamentals#
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
Calendar / Reminder | the system-design view | calendar-reminder |
LLD |
State machines | event lifecycle | state-machines |
LLD |
Behavioral patterns | Strategy for recurrence | behavioral-patterns |
LLD |
Pessimistic vs optimistic locking | conflict detection at commit | pessimistic-optimistic-locking |
Quick reference#
Classes#
- User (id, email, timezone)
- Calendar (interval tree of Events)
- Event (start, end, attendees, recurrence)
- RecurrenceRule (Daily / Weekly / Monthly)
- Scheduler (findSlot, schedule)
- ReminderService
Conflict detection#
Interval tree query O(log N + K) for events overlapping range.
Find earliest slot#
- Union busy intervals across all attendees in window
- Merge overlapping
- Walk gaps; return first gap ≥ duration
Recurrence#
- RFC 5545 RRULE for real apps
- Expand occurrences inside query window only (not all of time)
Timezones#
- Store UTC
- Display in user's zone
- Recurrence operates in user's tz; materialise to UTC for storage
Reminders#
Delayed queue (Redis ZSET, RabbitMQ TTL+DLX, or timer wheel).
Concurrency#
Optimistic version on Event for concurrent scheduling.
Extensions#
- Booking links (Calendly)
- Room resources
- Shared calendars + ACL
- Reschedule + cancellation versioning
Refs#
- RFC 5545 iCalendar
- Google Calendar API concepts
FAQ#
How does conflict detection work in a calendar scheduler?#
Each calendar uses an interval tree of events so an overlap query runs in O(log N + K), much faster than a linear scan of every event.
How do you find an available meeting slot for multiple attendees?#
Union the busy intervals of all attendees in the requested window, merge overlaps, then walk the resulting gaps and return the first gap of at least the requested duration.
How are recurring events stored?#
Store one event with a recurrence rule (RFC 5545 RRULE) and expand only the occurrences inside the query window, instead of materialising every future instance.
How should timezones be modelled for calendar events?#
Store event instants in UTC, display in the viewer's timezone, and run recurrence rules in the organiser's tz so daylight saving shifts behave naturally.
How are reminders delivered at scale?#
Push a delayed message into a Redis ZSET, RabbitMQ TTL+DLX, or timer wheel keyed by send time; workers pop ready entries and dispatch notifications.
Related Topics#
- Calendar / Reminder Service: the system-design sibling
- State Machines: event lifecycle modelling
- Behavioral Patterns: Strategy for recurrence rules
Further reading#
- RFC - RFC 5545 - iCalendar
- Doc - Google Calendar API model