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.
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
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
"""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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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
The calendar service's high-level design
Text description of the diagram
The diagram runs from top to bottom.
- 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.
- 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.
- 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.
- The time-zone rules come from the IANA tz database, and new releases are applied as they appear.
- The free/busy service takes busy instances from the expansion and returns merged busy periods without titles or details.
- 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.
- 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:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
The deep-dive questions
- How are a recurring meeting, its exceptions and an edit to “this and all following” stored?
- How is a rule expanded on read, and what happens on the days the clocks change?
- Why store local time and a zone id rather than UTC, and what happens when a government changes its rules?
- How is free/busy computed across calendars and zones, and why does it reveal so little?
- How do reminders fire once and on time while events keep changing?
- 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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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
- RFC 5545: Internet Calendaring and Scheduling Core Object Specification (iCalendar) (IETF)
- RFC 7529: Non-Gregorian Recurrence Rules in iCalendar (IETF)
- RFC 4791: Calendaring Extensions to WebDAV (CalDAV) (IETF)
- RFC 5546: iCalendar Transport-Independent Interoperability Protocol (iTIP) (IETF)
- RFC 6578: Collection Synchronization for WebDAV (IETF)
- Time Zone Database (IANA)
- News for the tz database (IANA (tz project))
- tz database source: asia (Asia/Kolkata) (IANA (tz project))
- PEP 495: Local Time Disambiguation (Python Software Foundation)
- datetime, basic date and time types (Python documentation) (Python Software Foundation)
- 15 U.S. Code § 260a: Advancement of time or changeover dates (Legal Information Institute, Cornell Law School)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress