CAPACITY ESTIMATION:
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
POST /v1/meetings
json
{
"title": "Sprint Planning",
"startTime": "2026-03-02T14:00:00Z",
"endTime": "2026-03-02T15:00:00Z",
"location": "Room 4B / https://zoom.us/j/123",
"participants": ["user_alice", "user_bob", "user_carol"],
"recurrence": null,
"reminders": [15, 60],
"idempotencyKey": "mtg_client_abc123"
}
Returns 201 with meeting ID, participant statuses (all initially "invited"), and any conflict warnings. The idempotencyKey is client-generated. If the client retries after a timeout, the server detects the duplicate key and returns the existing meeting instead of creating a new one.
GET /v1/meetings/{meetingId}, Full meeting details including participants with RSVP status, recurrence pattern, and reminders.
PUT /v1/meetings/{meetingId}, Update meeting time, participants, or details. If time changes, triggers re-availability check and re-notification. Partial updates supported via JSON merge patch.
POST /v1/meetings/{meetingId}/respond, Participant accepts, declines, or tentatively accepts. Body: {"status": "accepted"}. Updates the participant record and notifies the organizer.
GET /v1/availability?userIds=alice,bob,carol&startDate=2026-03-02&endDate=2026-03-06
Returns free/busy blocks for each user in the date range. Each block: {userId, start, end, status: "free"|"busy"|"tentative"}. The organizer's client overlays these to find common free slots. Lightweight response, only busy/tentative blocks are returned (free is implied by absence).
GET /v1/users/{userId}/calendar?view=week&date=2026-03-02
Returns all meetings for the requested view (day/week/month). Each meeting includes: title, start/end times, location, participant count, and the user's RSVP status. Paginated for month view. Response cached in Redis with the user's week key.
POST /v1/calendar/sync, Triggers sync with connected external calendars (Google, Outlook). Returns sync status and any conflicts detected.
GET /v1/calendar/export?format=ical, Exports the user's calendar in iCal format for import into other systems.
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
Read Path: client API --> cache --> database
Cache store the information about most recently visited events or time for a userID
Write Path:
client API call add --> event being sent to a message queue --> message queue update the database with (userID, startTime,endTime)
The message queue also do the fan out to push notification about user being added, if accepted, then add to database with their data
DATABASE DESIGN:
meeting, user, pariticipant
| users | user_id (PK), email (UNIQUE), name, timezone, notification_preferences (JSONB) | Timezone stored as IANA string (e.g., "America/New_York") |
| meetings | meeting_id (PK), organizer_id (FK), title, start_time (TIMESTAMPTZ), end_time (TIMESTAMPTZ), location, is_recurring, idempotency_key (UNIQUE per organizer) | All times in UTC; idempotency_key prevents duplicate creation |
| participants | meeting_id + user_id (composite PK), status, responded_at | Status: invited, accepted, declined, tentative |
| recurring_patterns | pattern_id (PK), meeting_id (FK), rrule, series_end_date | RRULE string (RFC 5545): "FREQ=WEEKLY;BYDAY=MO;INTERVAL=1" |
| recurring_exceptions | exception_id (PK), pattern_id (FK), original_date, replacement_meeting_id (FK, nullable) | Tracks cancelled or rescheduled individual occurrences |
| notifications | notification_id (PK), user_id (FK), meeting_id (FK), type, channel, scheduled_at, sent_at | Type: invite, update, reminder, cancellation |
PostgreSQL for all structured data, meetings, users, participants, recurring patterns. ACID transactions ensure that meeting creation atomically inserts into meetings, participants, and notifications tables. The tstzrange type and GiST indexes enable efficient overlap queries for availability checking: SELECT m.* FROM participants p JOIN meetings m ON p.meeting_id = m.meeting_id WHERE p.user_id = $1 AND tstzrange(m.start_time, m.end_time) && tstzrange($proposed_start, $proposed_end). Read replicas handle the 2,600 calendar reads/sec.
Redis for caching and scheduling, cached calendar views (user's weekly meetings) stored as serialized JSON with 1-hour TTL. Reminder scheduling uses a Redis sorted set: ZADD reminders {fire_at_timestamp} {notification_id}. A poller runs every 10 seconds, fetching due reminders with ZRANGEBYSCORE reminders 0 {now}. Also stores idempotency keys (SET with 24-hour TTL) and rate limiting counters.
Kafka for event streaming, meeting lifecycle events (created, updated, cancelled) published to Kafka topics. The Notification Service consumes events and delivers notifications. The Sync Service consumes events to push changes to external calendars. Decouples the Meeting Service from downstream consumers.
meetings(organizer_id, start_time), organizer's meetings in a time rangeparticipants(user_id, meeting_id) + join to meetings(start_time). A user's calendar viewmeetings(start_time, end_time) using GiST with tstzrange, overlap detection for availabilitymeetings(organizer_id, idempotency_key), UNIQUE index for duplicate preventionrecurring_exceptions(pattern_id, original_date), lookup exceptions for a specific occurrenceClient apps (web, mobile, desktop): Render calendar views (day/week/month), provide meeting creation forms with participant autocomplete, display notifications, and handle offline caching. The web client is the primary interface; mobile apps support push notifications for reminders.
API Gateway: Routes requests to backend services, handles JWT authentication, enforces per-user rate limits (token bucket), and provides TLS termination. Returns cached calendar views from Redis when available.
Meeting Service: The core service. Handles meeting CRUD, availability checking, conflict detection, recurring meeting expansion, and participant management. Writes to PostgreSQL in transactions. Publishes meeting lifecycle events to Kafka.
Notification Service: Consumes meeting events from Kafka and delivers notifications via email (SendGrid), push (FCM/APNs), and SMS (Twilio). Manages the reminder scheduling system: when a meeting is created, schedules reminders in a Redis sorted set. A poller fires due reminders on time.
Calendar Sync Service: Handles bidirectional sync with Google Calendar and Outlook. Listens to Kafka events to push local changes to external calendars. Polls or receives webhooks from external services for inbound changes. Resolves conflicts when the same event is modified on both sides.
Search Service: Elasticsearch-backed service for searching meetings by keyword, participant, date range, or meeting type. Indexes meeting data via CDC from PostgreSQL. Supports "find all meetings with Alice about budget in Q1."
Data stores: PostgreSQL (meetings, users, participants, recurring patterns), Redis (calendar cache, reminder scheduling, idempotency keys), Kafka (event streaming), Elasticsearch (search index).
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
Time-range overlap query with concurrent scheduling race condition handling
This is the heart of the calendar system. When an organizer creates a meeting for 2-3 PM with participants Alice, Bob, and Carol, the system must determine which participants have conflicting meetings in that time window. And handle the case where another organizer simultaneously schedules a conflicting meeting for the same participants.
Overlap query: The availability check uses PostgreSQL's range type: SELECT p.user_id, m.meeting_id, m.title FROM participants p JOIN meetings m ON p.meeting_id = m.meeting_id WHERE p.user_id IN ('alice', 'bob', 'carol') AND tstzrange(m.start_time, m.end_time) && tstzrange('2026-03-02 14:00Z', '2026-03-02 15:00Z') AND m.status != 'cancelled'. The && operator tests range overlap. With a GiST index on tstzrange(start_time, end_time), this query executes in under 10ms even with millions of meetings.
Concurrent scheduling race condition: Two organizers simultaneously create meetings at 2-3 PM for the same participant (Bob). Both run the availability check (both see Bob is free. Both create their meetings) Bob is double-booked. The system handles this with a post-creation conflict check: after INSERT, re-query for overlaps on the just-inserted meeting's participants. If a new conflict is detected (the other meeting was inserted between the check and the write), notify both organizers of the conflict. This is optimistic concurrency: accept the write and detect conflicts after, rather than locking Bob's entire calendar during the check-and-write window.
RRULE pattern stored once, occurrences pre-generated for a rolling window, exceptions for cancelled or rescheduled instances
Storage model: A recurring meeting has two parts: the pattern (stored in recurring_patterns as an RRULE string like FREQ=WEEKLY;BYDAY=MO;INTERVAL=1;UNTIL=20260901) and the occurrences (individual meeting rows in the meetings table, each with a recurring_pattern_id foreign key and an occurrence_date).
Pre-generation: A background job runs monthly, expanding RRULE patterns into individual occurrences for the next 3 months. A weekly standup creates 12 new meeting rows per expansion run. Each occurrence inherits the template's title, duration, participants, and location but has its own meeting_id, allowing independent modifications.
Exceptions: Modifying one occurrence (move Monday standup to Tuesday) creates a row in recurring_exceptions with original_date = 2026-03-09 and replacement_meeting_id pointing to the rescheduled meeting. Cancelling one occurrence creates an exception with replacement_meeting_id = NULL. The calendar view query LEFT JOINs exceptions to filter these correctly.
Common Pitfall
Do not generate all occurrences of a recurring meeting up to its end date. A weekly meeting with no end date could generate hundreds of rows that are never viewed. Pre-generate only a rolling 3-month window and expand monthly. This reduces storage by 40x compared to full generation and avoids massive wasted writes when series are cancelled early.
Modifying the series: Changing the series (e.g., moving all standups from 10 AM to 11 AM) updates the template and bulk-updates all future pre-generated occurrences that haven't been individually modified. Past occurrences are not changed (historical accuracy). The Kafka event triggers cache invalidation for all affected participants across all affected weeks.
Kafka events trigger notifications, Redis sorted set schedules reminders at precise times
Event-driven notifications: Meeting lifecycle events flow through Kafka. The Notification Service consumes meeting_created (send invites), meeting_updated (send updates), meeting_cancelled (send cancellations), and participant_responded (notify organizer). Each event contains the full meeting context. No need for the Notification Service to query the Meeting Service.
Reminder scheduling: When a meeting is created with reminders [15, 60], the Notification Service calculates fire times: meeting_start - 15min and meeting_start - 60min in UTC. These are inserted into a Redis sorted set: ZADD reminders {fire_at_unix} {notification_id}. A poller process runs every 10 seconds: ZRANGEBYSCORE reminders 0 {now} returns all due reminders. For each, the poller sends the notification and removes the entry with ZREM.
Scale consideration: At 27M notifications/day, the sorted set holds ~1.1M entries per hour of upcoming reminders. Redis handles this easily. The poller processes ~300 reminders per 10-second cycle during average load, and ~1,000/cycle during business hour peaks. If the poller falls behind, the backlog is self-correcting. The next poll fetches all overdue reminders in one batch.
Failure handling: If the Notification Service crashes, Kafka events accumulate. On restart, the consumer resumes from its last committed offset. No events lost. Reminder sorted set entries persist in Redis (AOF enabled). The poller picks up overdue reminders on restart, delivering them late rather than never. A reminder delivered 2 minutes late is better than a missed meeting.