Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design)  Module 29 – Case studies: platforms, analytics and developer tools

Design a calendar service

Design a calendar service: recurring events stored as rules, times that survive daylight-saving changes, shared availability and reminders on time.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free intro, Pro: 4 of 20 minutes free
Free: 20 million calendars, and the reads are the load. With a pass: the other 14 sections and 3 interview answers, about 16 minutes.

What you will learn

  • Estimate the views, stored series and reminder spike of a calendar for 20 million users
  • Store recurring events and their exceptions correctly, and expand them on read
  • Handle time zones and daylight-saving changes without moving anyone's meetings
  • Compute free/busy across calendars and zones and find common free slots
  • Schedule reminders that fire once and on time while events keep changing

Before you start

What you’ll learn · Before you start

What you will learn

  • Estimate the views, stored series and reminder spike of a calendar for 20 million users
  • Store recurring events and their exceptions correctly, and expand them on read
  • Handle time zones and daylight-saving changes without moving anyone's meetings
  • Compute free/busy across calendars and zones and find common free slots
  • Schedule reminders that fire once and on time while events keep changing

Before you start

On this page

A calendar service for 20 million users shows each person the meetings inside the days they open, in their own time zone, and must keep a weekly 09:00 meeting at 09:00 when the clocks change. It also tells a group when everyone is free, and reminds each attendee once, on time.

The core idea is to store a recurring meeting as one rule, not as a row per meeting: a start in local time with an IANA time-zone id, a recurrence rule in the iCalendar format, and a list of exceptions. The service expands the rule when someone reads it, only inside the window the view asks for, using the current time-zone rules, so a meeting at 09:00 in New York stays at 09:00 when the clocks change, and even when a government changes the rules for its clocks. Free/busy answers and reminders come from the same expansion.

20 million calendars, and the reads are the load

What to ask, and what the service must do

Ask whether calendars are personal only or shared, whether meetings recur and can be changed one at a time, which clients must work (the service’s own apps, or standard clients too), and how reminders are delivered. Assumed here: 20 million users, recurring meetings with exceptions, invitations between users, standard clients through CalDAV, and reminders by push and email.

Functional: create, edit and delete events, including recurring ones, changing one occurrence or every following one; show a day, a week or a month of one or several calendars in the viewer’s time zone; answer free/busy for a set of people and propose common free slots; send reminders and invitations, and keep standard clients in sync.

Each non-functional requirement, and where the first design meets it:

Non-functional requirement Where the first design meets it
Every occurrence at its organiser’s local time, across clock changes Series in local time with a zone id, placed by the current rules on each read
A week view within 200 ms at the peak Expansion only inside the window the view asks for
A reminder sent once, on time, never for a moved or cancelled event Only the next reminder of each series is kept, with the series’ version
Free/busy shows when someone is busy, never what they are doing Busy periods without titles

Checkpoint

Check: What the service must guarantee

  1. Question 1 of 2 Which non-functional requirement rules out answering free/busy by showing attendees each other's calendars?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Free/busy shows when someone is busy, never what they are doing

    Scheduling needs to know when people are busy, not what they are doing. So free/busy returns busy periods without titles, which is safe to share with everyone who schedules.

  2. Question 2 of 2 An organiser in New York sets a weekly meeting at 09:00 on Mondays in winter. A design converts the first meeting to UTC and repeats it every 7 × 24 hours. Which requirement does it break?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Every occurrence happens at the local time its organiser chose; after the clocks go forward it starts at 10:00 in New York

    A meeting planned at 09:00 in New York must stay at 09:00 on New York's clocks. 14:00 UTC is 09:00 there in winter but 10:00 after the clocks go forward, so the series is stored in local time with its zone.

The estimate: 7,400 views a second at the peak

Assume 300 stored events per user, 8 views or syncs a day per user, and 5 reminders a day per user. Work each figure out, then check it against the sheet:

Checkpoint

Work out the estimate

  1. Question 1 of 3 20 million users open or sync their calendars 8 times a day each, and the busiest hour runs at 4 times the day's average. How many views and syncs a second must the service take then?

    Type a number.

    A hint

    Find the average a second first; a day has 86,400 seconds.

    Show the answer: Question 1 of 3

    Answer: 7407 a second (anything from 6666 to 8148 counts)

    20,000,000 × 8 = 160 million a day, and 160,000,000 ÷ 86,400 ≈ 1,852 a second on average; 4 × 1,852 ≈ 7,407 at the peak. Each one expands the series it shows into the instances of its window, so reads, not writes, carry the load.

  2. Question 2 of 3 Users get 100 million reminders a day. The busiest hour holds 8 % of them, and 40 % of that hour's reminders fall due in the single minute before its meetings start on the hour. How many reminders fall due in that minute?

    Type a number.

    A hint

    Take 8 % of the day, then 40 % of that hour.

    Show the answer: Question 2 of 3

    Answer: 3200000 reminders (anything from 2880000 to 3520000 counts)

    100,000,000 × 0.08 × 0.4 = 3.2 million in one minute, about 53,000 a second if spread over that minute. Meetings start on round hours, so reminders arrive in spikes rather than at an even rate.

  3. Question 3 of 3 Which figure of the estimate sets the size of the notification service?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: The reminder spike, 3.2 million reminders due in one minute

    100 million reminders a day average about 1,160 a second, but 3.2 million fall due in the single minute before meetings start on the hour. The service is sized for that spike, which the reminder section measures.

The estimate sheet, from its assumptions Python · estimates/estimates.py
"""The calendar service's estimates from stated assumptions: change any of them and run it again."""
DAY = 86_400  # seconds

users = 20_000_000
series_each = 300  # stored events per user; a recurring meeting is one stored series, not one row per meeting
series_bytes = 1_500  # the event, its rule, its exceptions' index and its attendees
views_each = 8  # calendar views and client syncs a day per user
peak = 4  # the busiest hour against the day's average
meetings_planned = 2_000_000  # meetings scheduled a day, each needing the attendees' free/busy
attendees = 8  # people per scheduled meeting, on average
reminders_each = 5  # reminders a day per user
busiest_hour = 0.08  # share of a day's reminders that fall in its busiest hour
on_the_minute = 0.4  # share of that hour's reminders due in the one minute before meetings start on the hour

views = users * views_each
reminders = users * reminders_each
spike = reminders * busiest_hour * on_the_minute

print(f"Views and syncs: {views:,} a day, {views / DAY:,.0f} a second on average, {views / DAY * peak:,.0f} at the peak")
print(f"Stored series: {users * series_each:,}, {users * series_each * series_bytes / 1e12:.1f} TB")
print(f"Free/busy reads: {meetings_planned * attendees:,} calendars a day for scheduling")
print(f"Reminders: {reminders:,} a day; {spike:,.0f} due in the busiest minute, {spike / 60:,.0f} a second over that minute")

Output

Views and syncs: 160,000,000 a day, 1,852 a second on average, 7,407 at the peak
Stored series: 6,000,000,000, 9.0 TB
Free/busy reads: 16,000,000 calendars a day for scheduling
Reminders: 100,000,000 a day; 3,200,000 due in the busiest minute, 53,333 a second over that minute

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 estimates.py

Reads dominate: about 7,400 views and syncs a second at the peak, each expanding a handful of series, against 9 TB of stored series. The sharpest number is the reminder spike, 3.2 million reminders in one minute, because meetings start on round hours.

The first design, walked along one week view

Calendar apps call an API that stores events as rules; expansion with time-zone rules feeds free/busy and the reminder scheduler.Calendar appsweb, mobile, CalDAV clientsCalendar APIevents, rules, exceptions,sync tokensEvent storeseries in local time + zone id,rule, exceptions, attendeesTime-zone rulesIANA tz database,new releases appliedExpansion on readinstances inside therequested window onlyFree/busy servicemerged busy periods,no titles or detailsReminder schedulernext reminder of each series,event version in every jobNotification servicereminders and invitations,by push and emailviews, edits, syncwritesseriesUTC offsetsbusy instancesnext occurrence,recomputed on every changedue reminders

The calendar service's high-level design

Text description of the diagram

The diagram runs from top to bottom.

  1. Calendar apps on the web, on phones and through CalDAV clients send views, edits and sync requests to the calendar API, which handles events, recurrence rules, exceptions and sync tokens.
  2. The API writes to the event store, which keeps each series in local time with a time-zone id, with its rule, its exceptions and its attendees.
  3. Expansion on read takes the series from the event store and the UTC offsets from the time-zone rules, and returns only the instances inside the requested window.
  4. The time-zone rules come from the IANA tz database, and new releases are applied as they appear.
  5. The free/busy service takes busy instances from the expansion and returns merged busy periods without titles or details.
  6. The reminder scheduler takes each series' next occurrence from the expansion, recomputed on every change, and keeps the next reminder of each series with the event's version in every job.
  7. The scheduler hands due reminders to the notification service, which sends reminders and invitations by push and email.

The calendar API writes each series to the event store with its local start, its zone id, its rule, its exceptions and its attendees. A view asks for a window; the expansion step reads the series and returns only the instances inside that window, placing each one with the current time-zone rules. The free/busy service uses the same expansion and returns busy periods without titles. The reminder scheduler keeps the next reminder of each series and hands due reminders to the notification service, and invitations go to attendees as iCalendar messages. Step through one week view in Bengaluru, the week after New York’s clocks go forward:

Interactive · step by step One week view, from the request to the screen Step through one read. Each step names the part that acts and what it adds.
One week view, from the request to the screen, step 8 of 8. Showing Calendar app, Calendar API, Time-zone rules, Event store, Free/busy service, Expansion on read (1 instance: 13:00 UTC), Reminder scheduler.the week asked forher calendarsseriesUTC offsetsinstancesbusy timesnext occurrenceCalendar appAsha's week, in ISTCalendar APIthe week as a UTC windowTime-zone rulesthe current tz releaseEvent storeseries: local time + zoneFree/busy servicebusy periods onlyExpansion on read1 instance: 13:00 UTCReminder schedulernext reminder per series

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. The week asked for. Asha, in Bengaluru, opens the week after New York moved its clocks forward. The app sends the week in her zone, Asia/Kolkata, and the API turns it into one window in UTC, from Sunday 18:30 to the next Sunday 18:30, since India is 5 hours 30 minutes ahead.
  2. Series, not rows. The API finds the series her calendars show and reads them from the event store. Her team's weekly meeting comes back as one series, Mondays at 09:00 in the zone America/New_York, with its rule and its exceptions.
  3. Today's clock rules. Expansion takes each series with the current time-zone rules. New York moved its clocks forward on the Sunday before this week, so it is now 4 hours behind UTC instead of 5.
  4. Only inside the window. The rule is expanded for this window and no other. It gives one instance, Monday at 09:00 in New York, which is 13:00 UTC. No other week is computed, and nothing is written.
  5. Shown in her time. The app shows the meeting at 18:30 in Asha's time. The week before it was at 19:30. The organiser's zone changed its clocks and India did not, so the meeting stayed at 09:00 where it was planned.
  6. Free/busy, same instances. When a colleague looks for a time with Asha, the free/busy service uses the same expansion and answers with the busy period alone, 13:00 to 14:00 UTC, with no title and no attendees.
  7. One reminder ahead. The reminder scheduler keeps only the series' next reminder, 10 minutes before 13:00 UTC, with the series' version in the job. When it fires, the one after it is scheduled.
  8. Computed, never stored. The read used one stored series and the rules in force today. Every instance on the screen was computed for this window, so a change to the series or to the rules shows on the next read.

Checkpoint

Check: One week view through the first design

  1. Question 1 of 2 Which of these does a request for one week of a person's calendar need?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • The event store
    • The time-zone rules
    • The calendar API
    • Expansion on read

    A week view reads the series from the event store and expands them into that week's instances, using the time-zone rules to place each instance correctly. Reminders, notifications and invitations work beside the read path, so a backlog in them never slows a calendar view.

  2. Question 2 of 2 A weekly 09:00 meeting in New York is at 19:30 in India in winter. At which time does Asha, in Bengaluru, see it the week after New York moves its clocks forward?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 18:30

    Expansion places the 09:00 New York meeting with the current rules: 13:00 UTC after the change instead of 14:00. India keeps one offset, so Asha sees the meeting an hour earlier, at 18:30.

The deep-dive questions

  1. How are a recurring meeting, its exceptions and an edit to “this and all following” stored?
  2. How is a rule expanded on read, and what happens on the days the clocks change?
  3. Why store local time and a zone id rather than UTC, and what happens when a government changes its rules?
  4. How is free/busy computed across calendars and zones, and why does it reveal so little?
  5. How do reminders fire once and on time while events keep changing?
  6. How do standard clients stay in sync, and what gives way at ten and a hundred times the load?

Storing a recurring meeting: one rule and its exceptions Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Expanding a rule on read keeps 09:00 at 09:00, run
  • Times that happen never or twice need a written rule
  • A government’s new clock rules move instants, not meetings
  • Free/busy across zones: merge, then look for the gaps
  • Reminders that fire once and on time, measured
  • Sync, invitations and interoperability
  • Failure drills, and the load at ten and a hundred times
    • What breaks at ten and a hundred times the load
  • Options considered and rejected
  • What each level is expected to cover
  • A 45-minute self-mock
  • Practise: the whole design, one question at a time
  • Interview questions
    • Should a calendar store a recurring meeting's start time in UTC or in local time?
    • A weekly team meeting has run for a year with several one-off changes. The organiser now moves "this and all following" meetings from Monday to Thursday. How do you store the change, and what happens to the earlier one-off changes and to the attendees' replies?
    • A government announces, with seven weeks' notice, that its country will stop changing its clocks. Which events in your calendar service change, which stay the same, and how do you roll out the new rules?
    • Three million reminders fall due in the same minute at the start of each hour. Design delivery so that none fires twice, none fires for a moved or cancelled event, and the spike does not overload the push service.
  • Key takeaways

3 diagrams · 3 runnable examples (Python) · 3 interview answers · about 44 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Design a real-time leaderboard

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.