DEV Community

Cover image for "Design a Calendar System" Looks Easy. Here's Where Candidates Fail in the Interview
Crack Tech
Crack Tech

Posted on

"Design a Calendar System" Looks Easy. Here's Where Candidates Fail in the Interview

"Design Calendar System" sounds simple. Create a meeting, invite people, show a grid of boxes.

It isn't. Underneath sit some of the most interesting problems in system design: a read-heavy workload with a fan-out on every write, notifications that must reach every invitee, reminders that must fire at an exact minute for hundreds of millions of people, and recurring events that never really end.

This walkthrough follows the structure we coach for a 45–60 minute interview: requirements, estimates, APIs, data model, high-level design, then the deep dives where the real signal is, and finally the AI follow-up more interviewers now ask.

1. Requirements

Functional

  • Create, read, update and delete meetings.
  • Invite people by email, including people who don't use the product.
  • Show a user's calendar for a day, week or month.
  • Notify invitees when a meeting is created or changed.
  • Remind participants a few minutes before a meeting (e.g. 15 minutes).

Nice to have: find a free slot across several people (free/busy), recurring meetings, time zones.

Non-functional

  • Highly available: 99.99%. A calendar that's down means missed meetings.
  • Low latency: calendar views in well under 500 ms (p99).
  • Read-heavy: people look at their calendar far more often than they change it.
  • Scalable: hundreds of millions of daily users.
  • Consistency: eventual consistency is fine for invitees, but the organizer must see their own change immediately (read-your-writes).

Interview tip: say why each requirement matters. "Eventually consistent, except read-your-writes for the organizer" shows judgement; "eventually consistent" alone is a buzzword.

2. Back-of-the-envelope estimates

Assumptions:

  • 2B users
  • 500M daily active users (DAU)
  • 20% of DAU create 2 meetings a day, and 30% create 1
  • New meetings / day → 0.2 × 500M × 2 + 0.3 × 500M = 350M
  • ~4 participants per meeting → ~1.4B invites per day
  • Calendar views / day → ~10 opens per DAU → 5B

QPS planning:

  • Write QPS → 350M / 86,400 ≈ 3.5K/s average, ~10.5K/s peak
  • Read QPS → 5B / 86,400 ≈ 50K QPS average, ~150K/s peak
  • Read : write ratio ~15 : 1, so read-heavy

Capacity planning:

  • 1 KB meeting + ~100 Bytes per participant ≈ 1 KB + 4 × 100 Bytes = 1.4 KB
  • 1.4 KB × 350M ≈ 0.5 TB/day ≈ ~180 TB/year before replication (no media)

Interview tip: The numbers tell us where to spend effort: optimise reads, make writes reliable, and design the fan-out carefully.

3. APIs

POST   /meetings                      create (title, start, duration, tz, invitees)
                                      + recurrence?, Idempotency-Key header
GET    /users/{id}/calendar?start&end  meeting summaries for a day/week/month (paginated)
GET    /meetings/{id}                 full details
PATCH  /meetings/{id}                 update only the changed fields (If-Match: version)
DELETE /meetings/{id}                 cancel
POST   /meetings/{id}/invitees        add invitees
DELETE /meetings/{id}/invitees/{uid}  remove an invitee
POST   /meetings/{id}/rsvp            accept / decline / maybe
GET    /free?users&start&end          free blocks only (for slot finding)
Enter fullscreen mode Exit fullscreen mode

Two details interviewers notice:

  • The calendar view returns summaries, not just IDs. Returning IDs and then fetching details is an extra round trip on the hottest path.
  • Updates carry a version (optimistic locking). Even if only the owner can edit, they can edit from two devices at once.

How an update works. The client sends only the fields that changed, never the whole meeting row. Moving a meeting 30 minutes later looks like this:

PATCH /meetings/m_123
If-Match: "7"
Content-Type: application/merge-patch+json
{ "start": "2026-10-20T10:30:00Z" }
Enter fullscreen mode Exit fullscreen mode
  • Partial update. Fields that aren't sent stay as they are, and null clears a field. The payload is tiny, and a client can't accidentally overwrite a field it never touched.
  • Version check. If-Match: "7" means "apply this only if the meeting is still at version 7". The Meeting Service runs a conditional write: UPDATE meetings SET start_utc = …, version = 8 WHERE meeting_id = 'm_123' AND version = 7. If another device changed the meeting first, nothing is updated and the API returns 412 Precondition Failed. The client refetches, shows the user the newer meeting, and lets them retry. The response to a successful update returns the whole updated meeting with its new version (ETag: "8").
  • Invitees have their own endpoints. Adding or removing people writes participant rows and sends notifications, so it isn't hidden inside a generic PATCH.

4. Data model

Users

  • user_id (PK) → Shard Key
  • email (unique)
  • metadata

Meetings

  • meeting_id (PK) → Shard Key
  • owner_id
  • title
  • description
  • start_utc
  • duration
  • version
  • metadata
  • list

Participants

  • user_id → Shard Key
  • meeting_id
  • start_utc
  • duration
  • status (invited / accepted / declined)
  • PK(user_id, start_utc, meeting_id)

Why two tables? The two hottest queries want different partitioning:

  • "Show my week" → all of one user's meetings in a time range → partition by user_id, sort by time. One range scan on one shard. Once you get meeting_id, you can easily fetch meeting details based on meeting_id from meetings table.
  • "Change this meeting" → one meeting and all its participants → partition by meeting_id.

The participants table denormalises the start time so the calendar view never has to join. The cost: moving a meeting rewrites one participant row per invitee. Writes are rare and reads are hot, so that's a good trade.

Interview tip: Tables in the same DB can have different shard keys.
To fetch meeting details for a user, don't perform join between participants and meeting table, instead first fetch all the meeting Ids for the user within a day/week and then issue a separate query to DB to fetch meeting details. The Calendar Service does this server-side, so the client still gets summaries in one call.
Optimization → Cache meeting details for the current and next week. Older meetings are rarely read, so caching them wastes memory.

5. High-level design


Async path for notification. Every change in the participants table is captured with change data capture (CDC) into an event queue. Downstream consumers react to it:

  • Event Subscriber Service subscribes to a change and put notifications in a queue.
  • Change notifications (new invite, time changed, cancelled) are sent with immediately delivery to the delay queue. Delay queue with immediate delivery behaves as a FIFO queue.
  • The Reminder Notifications are scheduled with delay delivery.

Using CDC instead of having the Meeting Service publish events directly is deliberate. The database is the single source of truth, so an event can't be lost because a service crashed between "write the DB" and "publish the event".

What if the Participant table can't be updated in the same transaction as meetings table.

Use CDC to update participants table asynchronously.

6. Deep dive: keeping invitees up to date

When a meeting changes, invitees should see it if online. Two options:

Pull (periodic refresh)

  • Simple to build.
  • Wasteful: most refreshes return nothing new, and at 500M DAU that's a lot of empty requests.
  • Changes appear late.

Push

  • Precise and cheap on the network.
  • The server sends a tiny message, "meeting X changed", over Server-Sent Events or WebSocket to open web clients.
  • The client then fetches only what changed.

Use push, and keep a light pull on app open as a safety net (pushes can be missed when a device is offline).

7. Deep dive: reminders 15 minutes before

This is where strong candidates separate themselves. We need to fire hundreds of millions of reminders a day, at the right minute, exactly once, even when meetings move or get cancelled.

8. Where agentic AI fits, and where it doesn't

Interviewers now often add a follow-up: "How would you use AI here?" In agentic AI system design rounds it's sharper: "Which parts of this would you hand to an agent?" The strong answer starts by drawing a line.

Keep the core deterministic. The Calendar DB, CDC, the event queue, fan-out and the delay queue must be exact, fast and cheap. An LLM call takes a second or more, costs real money per request, and can answer the same input differently twice. Nobody wants a reminder that fires "probably around 9:45", or a fan-out that decides which invitees deserve a notification. None of these components should be replaced by an agent.

Put agents at the edges, where the input is human. The good fits are jobs that today need a person to read, judge or negotiate:

  • Finding a time: open everyone's calendar, compare, propose. With Agent → "Find 30 minutes with Priya and Alex next week, mornings, not Friday" → it checks free, ranks slots and drafts the invite
  • Rescheduling after a decline or a new conflict With Agent → Proposes new times to everyone, collects the replies and books the slot that works.
  • Empty meeting descriptions With Agent → Drafts an agenda from the email thread or last meeting's notes.

9. Common mistakes

  • One table for everything, so "show my week" becomes a scatter-gather across shards.
  • Not creating Participants table and read fetch meetings created but not invited. Participants table is required and Invitees, organizer both should be added as participants.
  • Relying on Redis key expiry for reminders.
  • Doing the maths and not using it. Estimates should drive decisions (read-heavy → denormalize data).
  • Putting an LLM in the core path, for example letting an agent decide who gets notified, or giving it direct database access.

Wrapping up

A great answer isn't the biggest diagram. It's a clear story: the numbers say it's read-heavy, so we shape the data for reads; writes fan out asynchronously through CDC; and reminders use a delay queue that survives retries and reschedules. AI belongs at the edges, where people talk to the system, not inside its core. Then you show you know where it breaks.

Preparing for system design interviews, from classic designs like this to agentic AI systems? Crack Tech offers private 1:1 coaching and realistic mock interviews scored the way a hiring committee reads them. Book a free 15-minute call at cracktech.app.

Top comments (0)