Knowledge Guide
HomeSystem DesignSystem Design Problems

medium Design Google Calendar

Image
Image

Let's design a calendar service similar to Google Calendar that lets millions of users schedule meetings and events. In essence, this service lets users create events, invite others, and reserve resources (like meeting rooms) just as one would write plans on a shared calendar. Key entities in the system include:

Google Calendar Key Entities
Google Calendar Key Entities

Analogy: If we compare to a real-world scenario, think of a personal assistant managing a physical planner. The assistant keeps track of each person’s schedule (Users and their Calendars), writes down meeting details (Events), sends out invites and tracks RSVPs (Invitations), finds an appropriate meeting room (Rooms), and checks everyone’s free/busy slots to avoid double-bookings (Availability). Our goal is to design an online service that performs all these tasks at internet scale (hundreds of millions of users).

Functional Requirements

The core features of the calendar service must cover common scheduling tasks:

Functional Requirements
Functional Requirements

Non-Functional Requirements

This service is global and mission-critical, so it must meet strict non-functional criteria:

Non-functional Requirements
Non-functional Requirements

Before diving into design, we estimate the scale to guide our choices:

Summary:

Details:

These estimations guide us to choose a distributed, scalable architecture with careful consideration for database sharding, caching, and load balancing.

We will use a REST API. We assume HTTPS for security.

1. Create Event

2. Get Calendar View

3. Update Event

4. RSVP to Event

At a high level, the calendar service will use a globally distributed microservices architecture. This means the system is composed of several specialized services that work together, deployed across multiple data centers around the world.

Architecture Overview

Clients (web browsers or mobile apps) connect to our service through a load-balanced API gateway. The gateway routes API calls (like “create event” or “fetch calendar events”) to the appropriate backend service. The core backend services include an Event Service (which handles event and invite logic), an Availability/Scheduling Service (for checking calendars and suggesting open slots or rooms), and a Notification Service (for sending out emails, push notifications, etc.). Data is stored in a Calendar Database which is optimized for fast writes and reads of event data. A high-level request path might look like:

Clients → Load Balancing → Service Layer → Data Stores:

High-level Design of Calendar Service
High-level Design of Calendar Service

Each component is replicated across multiple data centers. For instance, we might have data centers in Americas, Europe, and Asia; each has a full set of microservice instances and caching, while databases use cross-dc replication. Users connect to their nearest region, but if they schedule a meeting with someone in another region, the system’s backend services communicate over the network to coordinate (ensuring both users see the updated event).

Request Flow Example: To illustrate, consider the flow for creating a new event with invitees and a room: The client (user) fills in event details and hits “Save.” The request goes to the API Gateway, which forwards it to the Event Service. The Event Service first checks availability: it calls the Availability Engine to ensure the organizer, attendees, and desired room are free at that time. If free, the Event Service writes the new event into the Calendar DB (this might involve multiple writes – one for the event and entries for each attendee’s calendar or a transaction in a SQL DB). After saving, it produces messages to a queue for each invitee. The Notification Service picks up those messages and sends out invite notifications (e.g., an email to each invited user with the event details). The response goes back to the client confirming the event is created. Within a few seconds (or instantly, if using push), each invitee’s client device gets an update that a new event invite has appeared on their calendar. This end-to-end flow involves multiple components working in concert, as described above.

The following schema defines a SQL-compatible relational database for a calendar system. It supports multiple user calendars, event scheduling (including recurring events with exceptions), invitations/RSVPs, resource booking (rooms), and sharing with permissions, while handling time zones and notifications for reminders.

Each table is outlined below with its columns, data types, and descriptions:

Users

Stores user account information. Each user can own calendars and receive event invitations.

Field NameData TypeDescription
user_idINT (PK)Primary key, unique user identifier (auto-increment).
usernameVARCHAR(150)User’s username or display name (unique, not null).
emailVARCHAR(255)User’s email address (unique, not null).
password_hashVARCHAR(255)Hashed password for authentication (not null).
default_timezoneVARCHAR(50)User’s preferred time zone (e.g. America/Los_Angeles).

Calendars

Each calendar belongs to a user (the owner). Users can have multiple calendars (e.g. personal, work). Calendars can be shared with other users with specific permissions.

Field NameData TypeDescription
calendar_idINT (PK)Primary key, unique calendar identifier (auto-increment).
user_idINT (FK)Owner of the calendar. References Users.user_id (not null).
nameVARCHAR(100)Calendar name (e.g. "Work Calendar") (not null).
descriptionTEXTOptional description of the calendar (notes or purpose).
timezoneVARCHAR(50)Default time zone for events in this calendar (e.g. UTC or IANA zone name) (not null).
is_primaryBOOLEANWhether this is the user’s primary/default calendar (not null, default FALSE).

Foreign Keys: user_id → Users(user_id). Each calendar’s timezone can default to user’s preference; events can override timezone as needed.

Events

Stores individual events. Each event is associated with a calendar. Supports one-time events and recurring events. Times are stored in UTC with a specified timezone for correct local scheduling. An event may have an associated room reservation and may involve multiple attendees (invitations).

Field NameData TypeDescription
event_idINT (PK)Primary key, unique event identifier (auto-increment).
calendar_idINT (FK)Calendar that the event belongs to. References Calendars.calendar_id (not null).
titleVARCHAR(200)Title or summary of the event (not null).
descriptionTEXTDetailed description/notes for the event (optional).
start_timeTIMESTAMPStart date and time of the event in UTC (not null).
end_timeTIMESTAMPEnd date and time of the event in UTC (not null).
timezoneVARCHAR(50)Time zone of the event’s start/end time (e.g. America/New_York). Used for correct display and recurrence calculations.
is_all_dayBOOLEANIndicates an all-day event (TRUE if the event has no specific time, just a date).
locationVARCHAR(255)Text location or address of the event (optional).
room_idINT (FK)If set, references a Rooms.room_id for a reserved meeting room (nullable).
is_recurringBOOLEANTRUE if this event is a recurring series (has a recurrence rule). Default FALSE.
recurrence_rule_idINT (FK)If the event is recurring, foreign key to Event_recurrence_rules.recurrence_rule_id (NULL if one-time event).
statusVARCHAR(20)Status of the event (e.g. 'confirmed', 'tentative', 'cancelled'). Default 'confirmed'.

Foreign Keys: calendar_id → Calendars(calendar_id), room_id → Rooms(room_id), recurrence_rule_id → Event_recurrence_rules(recurrence_rule_id). Notes: The combination of start_time, end_time, and room_id should be checked to prevent double-booking a room (no overlapping events for the same room). Event times are stored in UTC; the timezone field preserves the original time zone context of the event for display and for computing recurring instances across DST or zone changes. For all-day events, times might be stored normalized (e.g., midnight to midnight) and is_all_day = TRUE. The status field can use an ENUM or check constraint to allow only valid values.

Event Recurrence Rules

Defines recurring event patterns. Each recurring event (series) has one recurrence rule describing how it repeats. This allows infinite or long-term repetition without storing every occurrence, for scalability. If an event is non-recurring, it will not have an entry here.

Field NameData TypeDescription
recurrence_rule_idINT (PK)Primary key, unique recurrence rule identifier (auto-increment).
event_idINT (FK)Event that this recurrence rule applies to. References Events.event_id (not null, unique).
frequencyVARCHAR(20)Recurrence frequency (e.g. 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY').
intervalINTInterval for the frequency (e.g. 2 = every 2nd week). Default 1 (every frequency period).
days_of_weekVARCHAR(20)If weekly recurrence, which days of week it occurs (e.g. 'MON,WED,FRI'). Null/unused for other frequencies.
day_of_monthINTIf monthly recurrence on a specific day, 1-31 for the day of month (or negative for reverse count, e.g. -1 = last day). Null if not applicable.
month_of_yearINTIf yearly recurrence on specific month (1-12). Null if not applicable or for other frequencies.
end_dateDATEDate on which the recurrence ends (no occurrences after this date). NULL if the series has no specific end date.
occurrence_countINTTotal number of occurrences for this event series (if it ends after a fixed count). NULL if not limited by count.

Foreign Key: event_id → Events(event_id) (each recurring event has one rule). Notes: The rule can express common patterns. For example, a weekly event every Monday and Wednesday would have frequency='WEEKLY', interval=1, days_of_week='MON,WED'. A monthly event on the 15th of each month would use frequency='MONTHLY', day_of_month=15. An event that repeats yearly every March 10 would use frequency='YEARLY', month_of_year=3, day_of_month=10. Either end_date or occurrence_count (or neither) can be set to define when the series stops (if both are NULL, the series is effectively unbounded). This table allows the application to compute upcoming occurrences on the fly, rather than storing each occurrence, which greatly improves scalability for long-recurring events. (Complex recurrence rules beyond these fields can also be stored as a text rule or handled by business logic if needed.)

Event Exceptions

Handles modifications or cancellations of specific occurrences in a recurring event series. When a particular occurrence of a recurring event is changed or skipped, an exception record is created. This prevents that occurrence from following the normal recurrence pattern and optionally links to a modified event instance. This table ensures that recurring events can have custom edits or deletions for specific dates.

Field NameData TypeDescription
event_exception_idINT (PK)Primary key, unique exception record identifier (auto-increment).
event_idINT (FK)References the recurring Events.event_id that this exception pertains to (not null).
exception_dateTIMESTAMPThe date/time of the occurrence that is affected by the exception (in UTC, corresponds to what the series’ occurrence start would have been).
alternate_event_idINT (FK)If this occurrence was modified (rescheduled or edited), references the new Events.event_id that replaces this occurrence. NULL if the occurrence is simply canceled with no replacement.
is_cancelledBOOLEANIndicator if the occurrence is cancelled (TRUE if this occurrence is skipped entirely). If an alternate_event_id is provided, the original occurrence is considered canceled/replaced by the alternate event (not null, default FALSE).
created_atTIMESTAMPTimestamp when this exception was recorded (default current).

Foreign Keys: event_id → Events(event_id), alternate_event_id → Events(event_id). Notes: For a recurring event series, this table lists any dates that deviate from the normal pattern. If an occurrence is cancelled, an entry is added with exception_date and is_cancelled = TRUE (and alternate_event_id NULL), so the system knows to skip that date. If an occurrence is modified (e.g. time or details changed for one instance), an exception entry is added with exception_date and an alternate_event_id pointing to a new event record that holds the details of that one-off occurrence (and is_cancelled can be FALSE or TRUE as a flag that the original recurrence instance is replaced). This approach (sometimes called an exclusion record with an alternate instance) allows editing one instance without affecting the entire series. Past occurrences that were part of a series could also be recorded here or as separate events if maintaining history, but typically the existence of an exception entry or alternate event is enough to know what happened for that occurrence.

Invitations

Tracks event invitations (attendees) and their RSVP status. This table links users to events they are invited to (other than the event owner). It supports managing responses like accepted or declined.

Field NameData TypeDescription
invitation_idINT (PK)Primary key, unique invitation identifier (auto-increment).
event_idINT (FK)Event to which the user is invited. References Events.event_id (not null).
user_idINT (FK)The invited user (attendee). References Users.user_id (not null).
statusVARCHAR(20)Invitation status / RSVP ('pending' = invited no response, 'accepted', 'declined', 'tentative'). Default 'pending'.
responded_atTIMESTAMPTimestamp when the invitee responded (null if not responded yet).
created_atTIMESTAMPTimestamp when the invitation was sent/created (default current).

Foreign Keys: event_id → Events(event_id), user_id → Users(user_id). Notes: There is one record per invitee per event. The combination (event_id, user_id) should be unique to avoid duplicate invites to the same user. The event’s owner/organizer is typically the calendar’s user and doesn’t need an invite entry (by definition they are hosting). The status field can be managed via an ENUM or set of allowed values. This table makes it easy to query who is invited to an event and their responses. If an invitee accepts an invitation, the event would appear on their calendar view (through their association in this table, or the system could create a copy on their calendar — but here we assume a single event record with invites). Invitation statuses can be updated as users respond (with responded_at set accordingly).

Rooms

Represents meeting rooms or resources that can be reserved for events. This table stores details of rooms and is referenced by events that book a room. It helps manage resource scheduling (e.g., conference rooms to avoid double-booking).

Field NameData TypeDescription
room_idINT (PK)Primary key, unique room identifier (auto-increment).
nameVARCHAR(100)Name of the room (e.g. "Conference Room A") (not null, possibly unique per location).
locationVARCHAR(255)Location details (e.g. office/building name or address of the room).
capacityINTCapacity of the room (number of people it can accommodate).
is_availableBOOLEANAvailability status of the room (TRUE if available for booking, FALSE if out of service). Default TRUE.
created_atTIMESTAMPTimestamp when the room was added to the system (default current).
updated_atTIMESTAMPTimestamp of last update to room info (e.g., capacity or status change).

Notes: This table is used in conjunction with Events.room_id. When creating or updating an event with a room, the system should ensure the chosen room is available and not already booked at that time. While the database cannot enforce non-overlapping reservations by itself (because that’s a temporal constraint), an application-level check or a database trigger can be used to prevent overlapping events for the same room_id. For convenience, frequent queries might index the name or (location, name) for quick lookup of rooms, and events may be queried by room_id to find schedules for a room.

Notification Queue

Stores scheduled notifications (email/SMS reminders, event alerts, etc.) to be sent to users. This can be used by a background job to send out reminders for upcoming events or invitation emails, ensuring timely notifications.

Field NameData TypeDescription
notification_idINT (PK)Primary key, unique notification entry (auto-increment).
user_idINT (FK)User who should receive the notification. References Users.user_id.
event_idINT (FK)Related event for the notification (if applicable, e.g. event reminder). References Events.event_id.
notify_timeTIMESTAMPThe date and time when the notification should be sent (UTC).
methodVARCHAR(20)Notification method (e.g. 'email', 'sms', 'popup').
statusVARCHAR(20)Delivery status of the notification ('pending', 'sent', 'failed'). Default 'pending'.
created_atTIMESTAMPTimestamp when the notification was queued (default current).
sent_atTIMESTAMPTimestamp when the notification was sent (null until delivered).

Foreign Keys: user_id → Users(user_id), event_id → Events(event_id). Notes: This table is optional and would be populated when a user schedules a reminder or when an invitation needs to be emailed. For example, if an event has a 30-minute reminder set, a row is added with notify_time = 30 minutes before event start. A background service would query for status='pending' notifications where notify_time is due or past, send the notification via the specified method, then update status to 'sent' and set sent_at. Indexes on notify_time and status would help efficiently find due notifications. This design decouples sending logic from the main tables and helps scale the notification delivery.

Shared Calendar Access (ACLs)

Manages sharing of calendars between users through Access Control Lists. Each entry grants a user certain access rights to a calendar (other than their own). This allows calendars to be shared read-only or with edit permissions.

Field NameData TypeDescription
share_idINT (PK)Primary key, unique share/permission record (auto-increment).
calendar_idINT (FK)Calendar that is shared. References Calendars.calendar_id (not null).
user_idINT (FK)User who is granted access to the calendar. References Users.user_id (not null).
access_levelVARCHAR(20)Access level granted (e.g. 'owner', 'editor', 'viewer' or 'read'/ 'write'). Defines permissions (owner/full control, edit rights, or view-only).
granted_atTIMESTAMPTimestamp when access was granted (default current).

Foreign Keys: calendar_id → Calendars(calendar_id), user_id → Users(user_id). Notes: The combination (calendar_id, user_id) should be unique (a user should have at most one access entry per calendar). The access_level can be implemented as an ENUM or a set of allowed strings (for example: 'viewer' = read-only, 'editor' = read/write, 'owner' = manages sharing and events). By default, the calendar’s user_id is the owner (with implicit full access); other users can have additional entries here if the calendar is shared with them. This table makes it easy to query which users can see or edit a given calendar. For instance, if a calendar is public to an organization, many entries could grant 'viewer' access. Proper indexing on calendar_id (and potentially user_id) helps quickly check permissions when a user tries to view or modify a calendar or its events.

Design Considerations: This schema uses normalized tables to capture different aspects of a calendar system. By separating recurring event rules and exceptions, the design avoids storing large numbers of duplicate event rows for each occurrence, ensuring scalability for long or indefinite recurring events. Instead, occurrences can be generated on the fly using the rule, and specific changes are applied via exceptions. All date/time fields are handled carefully with time zones: storing timestamps in UTC for consistency and storing the relevant time zone where needed to display events at correct local times. Foreign keys enforce referential integrity (e.g., an event’s calendar must exist, an invitation’s event and user must exist, etc.), and cascading deletes could be used as appropriate (for example, deleting a calendar could optionally delete its events and related invites). Indexes (not explicitly listed above) would be added on key fields such as primary keys (automatically indexed), foreign keys (calendar_id, user_id, etc.), and time fields used in lookups (e.g., an index on Events (calendar_id, start_time) to quickly find a user’s events in a date range). This ensures queries like “fetch all events for user X’s calendars in the next week” or “find if room Y is free at time Z” are efficient. The schema supports core Google Calendar–like functionality and can be extended with additional features (such as event reminders preferences, calendar colors, etc.) as needed, while maintaining clarity and normalization in the design.

Storage Choice: We have a few options for implementing the above data model at scale. A common approach is to use a relational database (SQL) for these tables to ensure consistency (ACID transactions can ensure an event and all its invite rows are saved together). We would then shard this database by user or by event ID to handle scale (more on sharding later). Alternatively, a distributed NoSQL store (like Google’s Spanner or Apache Cassandra) can hold events keyed by user_id for horizontal scale. In practice, a hybrid approach could make sense: use a SQL DB for core data but partitioned by user range or geographic region, and use NoSQL/ElasticSearch for features like full-text search of events or quick free/busy queries. The key is to avoid any single monolithic DB for all 100M users – instead, distribute the data. For instance, partitioning by user_id ensures all of a user’s events reside in the same shard, which makes retrieving a user’s calendar efficient and allows geographic localization of data. We also replicate data across regions for reliability. We might store each event once and have attendees query it, or store a copy of the event per user to make reads local – the design can go either way depending on consistency needs (storing copies per user means updates fan-out, storing one event means cross-user access on reads). A balanced design is to store one primary copy (under the organizer’s shard) and pointers for attendees, or use a distributed transaction to insert event records into each attendee’s shard at creation time (ensuring each user’s calendar has the event). This duplication speeds up reads at the cost of more complex writes (we’d need to update all copies on changes, possibly via asynchronous means).

Data Partitioning: Each of these tables will be large (especially Events and Invitations). We cannot keep all user events in one database instance. We will likely shard these tables by user or by some key. A common approach is to shard events by the organizer’s user_id (so all events created by a user go to the same shard), or by calendar_id. This way, when a user loads their calendar, the system mostly needs to pull data from one shard (for that user’s events and invites). We may need cross-shard queries for multi-user free/busy checks, which can be handled by aggregating results from relevant shards or by using a separate Availability service.

In this section, we will dig deeper into how each component handles the core features (event lifecycle, invitations, room booking, etc.).

Event Lifecycle Management

Managing events involves creating new events, updating them, handling recurring events, and deleting/cancelling events. It also involves the invite/response workflow. Here’s how the system handles these:

Availability and Meeting Room Suggestions

One of the key features is helping users schedule meetings at a time and place that works for everyone. This is handled by the Availability Engine and related logic:

Detailed Design of Calendar Service
Detailed Design of Calendar Service

Notification and Reminder Delivery

The Notification Service ensures users are kept informed of invites and upcoming events. Key points in this component:

The Notification system is crucial for a “real-time feel” and to keep users engaged and informed without requiring them to constantly refresh.

Timezone Handling

Timezone support is a critical aspect of a global calendar. The challenges are: storing times in a consistent format, displaying in the user’s local timezone, and handling daylight savings changes.

In summary, timezone support means storing timezone metadata and always converting to/from UTC on input/output. It’s essential for a global user base to have correct times.

Additional Considerations

Example Schema (Simplified)

To tie it together, here is a simplified schema snippet showing some tables (for illustration):

Users Table (Users):

user_id (PK)nameemailtimezone
101Alice Chenalice@xyz.comAmerica/Los_Angeles
102Bob Singhbob@xyz.comEurope/London

Calendars Table (Calendars):

calendar_id (PK)owner_id (FK Users)namevisibility
201101Alice - Workprivate
202101Alice - Personalprivate
203102Bob - Workprivate
3001(null, resource)Conf Room Apublic (resource)

Events Table (Events):

event_id (PK)calendar_id (FK)titlestart_time (UTC)end_time (UTC)organizer_idroom_id (FK Room)recurrence_rulestatus
5001201Team Meeting2025-05-20T16:00Z2025-05-20T17:00Z101 (Alice)3001 (Conf Room A)FREQ=WEEKLY; BYDAY=TUconfirmed
5002203Client Call2025-05-21T09:00Z2025-05-21T09:30Z102 (Bob)NULLNULLconfirmed

(Event 5001 is a weekly team meeting every Tuesday at 9am PDT by Alice, reserving Conf Room A. Event 5002 is a one-time call by Bob.)

Invitations Table (Invitations):

invite_id (PK)event_id (FK Events)invitee_id (FK Users)response_status
90015001101 (Alice - organizer)accepted
90025001102 (Bob)pending
90035001103 (Charlie)pending
90045002102 (Bob - organizer)accepted
90055002101 (Alice)accepted

(Invitations: Event 5001 has Alice, Bob, Charlie; Alice’s own invite can be considered accepted automatically. Event 5002 has Bob and Alice.)

Rooms Table (Rooms):

room_id (PK)namelocationcapacityfeatures
3001Conf Room A1st Floor, Bldg 510["Projector","VC"]
3002Conf Room B1st Floor, Bldg 54["Whiteboard"]

(Rooms can optionally also have an associated calendar or be tied via events.room_id for bookings.)

This schema allows us to retrieve a user’s events by looking at events where their calendar_id = user’s calendar OR events where they appear in Invitations as invitee. We would likely create a view or a combined query to get “all events for user X” as those they organized (in their calendar) plus those they were invited to.

Now that we have covered data and core logic, we can turn to how we will scale and meet the performance targets.

Designing for 100M+ users globally requires careful strategies in data distribution, caching, and fault tolerance. Below are the key strategies we employ to ensure the system scales and performs well:

By applying all these strategies – sharding, caching, load balancing, eventual consistency with fast convergence, idempotent operations, and robust failover – our calendar service can achieve massive scale while delivering a fast and reliable user experience. We have essentially a web-scale, real-time collaborative system that provides the backbone for users to manage their time effectively. With this design, the service will be ready to support hundreds of millions of users scheduling billions of events, across the globe, with the performance and reliability expected of a top-tier calendar platform.

🤖 Don't fully get this? Learn it with Claude

Stuck on Design Google Calendar? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Design Google Calendar** (System Design). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Design Google Calendar** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Design Google Calendar**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Design Google Calendar**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes