NEW: ML Mock & Coaching now available

Questions

Google Calendar

GoogleMeta

Design Google Calendar - a comprehensive system design interview question covering event management, recurring events, real-time sync, and availability checking.

45 min read

Staff+ Engineer: 12+ years of experience in real-time collaborative systems and calendar infrastructure. Tech lead for event management platforms serving billions of users. Engineering Manager: Over 12 years of industry experience with 6 years in engineering leadership, building and scaling tech infrastructure teams that deliver end-to-end large-scale distributed systems.

Challenge

Think Beyond the Happy Path

Calendar systems aren't just about CRUD operations on events — they're about recurring event expansion, multi-device sync, and fast availability checking across complex schedules.

Before diving into the material, take a moment to ask yourself:

  • Do you know how to handle recurring events — should you store every instance, or expand them on-the-fly? What happens when someone modifies "this and all future events"?
  • Do you know how to support multi-device sync so changes appear instantly across phone, laptop, and web — what sync protocol would you use?
  • Do you know how to quickly check availability and suggest meeting times for a group of 10 busy executives — without scanning millions of events?
  • Do you know how to handle timezone edge cases like events that span DST transitions or users traveling across time zones?

You don't need to answer all of these right away. A Senior/Staff+ Engineer doesn't stop at basic functionality — they anticipate edge cases, design for resilience, and push for production-grade reliability.

But great systems start with great questions. What would you ask next?

Problem Statement

Design a calendar service that:

  1. Allows users to create, modify, and delete calendar events
  2. Supports invitations and RSVP tracking
  3. Provides calendar viewing with weekly/monthly views
  4. Enables free/busy availability checking across users

Functional Requirements

FR1 – Event Management (Create / Modify / Delete)

Users can create events on their calendars with basic details (title, time, all-day flag, description, location), update those details later, and delete events they own. Only the event organizer (or calendar owner) can modify or delete an event.

FR2 – Invitations and RSVP

Users can add guests to an event and the system records each guest’s response as Yes/No/Maybe. Guests can update their own response, and the organizer can always see the current RSVP status for all invitees.

FR3 – Calendar Viewing (Weekly by Default)

Users can view their schedule in a calendar UI, with the weekly view as the default. The weekly view shows all events for that week and allows users to navigate to other weeks, open event details, and start creating new events from empty time slots.

FR4 – Free/Busy and Availability Checking

Users can check other users’ availability over a given time window, seeing when they are free or busy without necessarily seeing event details. This free/busy view helps organizers choose suitable time slots for meetings while respecting each user’s visibility settings

Non-Functional Requirements

NFR1 – Strong Consistency (CAP: Consistency over Availability)

The calendar backend is a single source of truth: once an event or RSVP is saved, all clients should see the same canonical state. Under network partition or quorum loss, we prefer failing writes rather than accepting conflicting versions of the same event.

Why Consistency over Availability in this design?
  • Impact of errors is worse than impact of failures: if an event silently “forks” (two times/guest lists both accepted), you get double-bookings and missed meetings. That feels like data corruption and destroys trust in the calendar. A transient “failed to save, please retry” is annoying but recoverable.
  • Partitions are rare, events are core: DB/quorum partitions are exceptional; event correctness is exercised constantly. Optimizing for the rare case (AP) at the cost of everyday correctness (C) is the wrong trade here.
  • Our other features depend on a single truth: free/busy computation, time suggestions, and multi-device sync all assume one canonical version of each event. If we allowed divergent writes, we’d need complex reconciliation logic that leaks into RSVP state, availability results, and device views.
  • Offline is handled at the edge, not by weakening server guarantees: when a user is offline, we queue changes locally and reconcile on reconnect with version checks. We do not relax the server’s consistency just because clients may be disconnected; this keeps the core store clean and predictable.

NFR2 – Multi-Device Real-Time Sync

The same account can be signed in on multiple devices, and changes must stay aligned across them with predictable, near-real-time behavior.

  • Cross-device propagation: Changes made on one online device appear on other online devices within 3–5 seconds (p95).
  • Offline edits: Edits made offline are queued and reconciled on reconnect, with explicit conflict surfacing if a newer version exists.

NFR3 – High Scalability

The system must sustain large user bases and long event histories while maintaining performance as read-heavy traffic grows.

  • Scale assumption: Up to tens of millions of users, each with hundreds of recurring series and thousands of events per year.
  • Traffic profile: Optimized for reads ≫ writes, with strong spikes during business hours and meeting-heavy periods.

NFR4 – Low Latency

Common user interactions should feel instant, especially viewing schedules and checking availability.

  • Views: Day/Week/Month view responses should be < 200 ms (p95).
  • Availability: Free/busy queries for up to ~10 users over a 1–2 week window should be < 500 ms (p95).

APIs & Entities

Note on Structure – Why APIs & Entities Are Separate This Time?

In most ShowOffer delivery frameworks, we walk each FR vertically (FR → APIs → Entities → Workflow → Diagram). For this calendar system, I’ve intentionally pulled APIs and Entities into shared sections instead of repeating them inside each FR.

There are two main reasons:

  1. Heavy cross-FR reuse of the same entities and APIs

    In this problem, almost all FRs depend on the same core objects: Event, EventException, Invitation, FreeBusyBlock, and ChangeLog. The same APIs (e.g., GET /v1/events, GET /v1/events?start_ts=&end_ts=, POST /v1/freebusy) are used by multiple FRs: FR1 for CRUD, FR3 for views, FR4 for free/busy. If we followed the standard per-FR layout, we’d either duplicate these definitions four times or constantly say “same as FR1,” which adds noise but no insight.

  2. Entities evolve as later FRs are introduced

    The data model for a calendar is central and grows over time: FR1 defines Event and EventException, FR2 adds Invitation, FR4 adds FreeBusyBlock, and all of them write to ChangeLog. If entity tables lived inside each FR section, every refinement (e.g., adding rsvp_status or changing recurrence_rule) would require editing multiple places, increasing the risk of inconsistent diagrams and descriptions. A single Core Data Model section keeps entities canonical and lets FR sections focus on how they use those entities, not re-explain what they are.

Practically, this structure is still interview-friendly: verbally, I can walk each FR vertically (APIs → key entities → workflow), but in the written version I centralize APIs and Entities to avoid duplication and make it easier for the interviewer to see the big picture of the system at a glance.

Each FR maps to one or more API endpoints. Here we define signatures only, not full request/response payloads. Use the copy button to grab any endpoint. FR1 – Event Management (Create / Modify / Delete)

Create event

POST /v1/events

Creates a new event on the authenticated user's calendar. If recurrence_rule is omitted or null, this creates a one-time event; if recurrence_rule is provided (RRULE-style), this creates a recurring event series.

Get event

GET /v1/events/{event_id}

Returns full details of a single event.

Update event

PATCH /v1/events/{event_id}?scope=single|this_and_future|series

Updates event fields; scope controls how recurring events are affected.

Delete event

DELETE /v1/events/{event_id}?scope=single|this_and_future|series

Deletes/cancels an event; scope controls recurring behavior.

FR2 – Invitations and RSVP

Add or update invitees (organizer)

POST /v1/events/{event_id}/invitations

Adds new guests or updates the guest list for an event.

List invitees and RSVP status (organizer)

GET /v1/events/{event_id}/invitations

Returns all guests and their current RSVP status.

Update RSVP (guest)

PATCH /v1/events/{event_id}/rsvp

Authenticated user sets or updates their RSVP (Yes/No/Maybe) for the event.

FR3 – Calendar Viewing (Weekly by Default)

List events in a time range

GET /v1/events?start_ts={start}&end_ts={end}

Returns all events on the authenticated user's calendar within the given time window; the client uses this for weekly (default) and other views.

Get single event

GET /v1/events/{event_id}

(Same as FR1) Used when user clicks an event in the calendar view to see details.

FR4 – Free/Busy and Availability Checking

Free/busy for multiple users

POST /v1/availability

Given a list of user IDs and a time window, returns each user's busy intervals (no event details). This is used to power free/busy views in the UI and to manually choose suitable time slots for meetings.

Tip

Why POST not GET in checking availability?

Semantically it’s a read, so we could expose GET /v1/availability with query params, but in practice we’d use POST because the request shape (multiple users, time windows, optional constraints) fits a JSON body much better and avoids URL length / encoding headaches.

UserRepresents an account in the system; the owner of exactly one calendar.
NameDescription
user_idPrimary key; unique identifier for the user.
emailUser's login / contact email.
nameDisplay name.
default_timezoneDefault timezone for events and views.
CalendarRepresents the user's single personal calendar (1:1 with User). All /v1/events APIs operate on this calendar for the authenticated user.
NameDescription
calendar_idPrimary key; identifier for the calendar.
owner_user_idFK → User.user_id; unique (one calendar per user).
nameCalendar display name (e.g., "My Calendar").
timezoneDefault timezone for events on this calendar.
EventBacks all event CRUD APIs and calendar views (POST/GET/PATCH/DELETE /v1/events, GET /v1/events?start_ts&end_ts). A row can represent a single event or a recurring series; recurrence semantics and per-occurrence overrides are detailed in Deep Dive 1.
NameDescription
event_idPrimary key; identifier for the event or recurring series.
calendar_idFK → Calendar.calendar_id; calendar this event belongs to.
organizer_user_idFK → User.user_id; user who owns/organizes the event.
titleEvent title (e.g., "Team Sync").
descriptionOptional free-text notes.
locationOptional location (room, address, link).
start_tsStart timestamp in UTC (base start for a one-off event or series).
end_tsEnd timestamp in UTC.
is_all_dayWhether the event spans the whole day.
timezoneTimezone for rendering this event in the UI.
recurrence_ruleNullable; when set, indicates this row is a recurring series (RRULE-style; full modeling in Deep Dive 1).
statusACTIVE or CANCELLED (soft delete / cancellation).
versionInteger for optimistic concurrency on PATCH/DELETE.
Info

For recurring events, the Event row acts as the series definition. Per-occurrence overrides (e.g., "this instance only moved/cancelled") are modeled via additional tables introduced in Deep Dive 1 (e.g., EventException).

InvitationRepresents each invitee (including guests) for an event, with their RSVP state; this is how FR2 APIs attach invitees to events.
NameDescription
invitation_idPrimary key; identifier for this invitation record.
event_idFK → Event.event_id; event the guest is invited to.
guest_user_idFK → User.user_id; invited user (nullable if external only).
guest_emailEmail of invited guest (used for external/lookup).
rsvp_statusYES, NO, MAYBE, or PENDING.
response_tsTimestamp of the last RSVP update.
is_organizerBoolean; true if this row represents the organizer as an attendee.
Tip

Organizer is on the Event; all invitees (including organizer, if we want) live in Invitation. That keeps invitee data normalized and lets FR2 evolve without bloating the Event row.

FreeBusyBlock (Optional)In the simplest design, FR4 POST /v1/availability can compute free/busy directly from Event + Invitation by expanding relevant events in the requested window. At scale, we introduce a derived FreeBusyBlock store to precompute busy intervals per user and keep availability queries fast.
NameDescription
user_idFK → User.user_id; user this busy block belongs to.
start_tsStart of busy interval in UTC.
end_tsEnd of busy interval in UTC.
statusBusy type (e.g., BUSY, OOO, TENTATIVE).
sourceOptional reference (e.g., event_id or type) for debugging.

Sign in to continue reading

"Google Calendar" requires a free account to access.

Sign in to continue