Skip to content

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#

  1. Union busy intervals across all attendees in window
  2. Merge overlapping
  3. 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.

Further reading#