How Google Calendar Finds Common Free Slots Instantly
One "Find a time" click. Behind it: precomputed availability bitmaps, bitwise AND across attendees, timezone normalization, and a scheduling engine that returns results in milliseconds — for millions of users with billions of events.
One button. A surprisingly hard scheduling problem.
You're setting up a meeting with five colleagues across three time zones. You click "Find a time." In under a second, Google Calendar shows you three open slots — everyone's free, the duration fits, and it respects working hours.
At first glance this sounds easy: just check everyone's calendar and find empty time. But at scale, the backend may need to process thousands of events per attendee, handle recurring meetings that expand into hundreds of instances, support real-time edits, normalize time zones, and return results in milliseconds.
The core challenge: A naive approach scans every event for every attendee on every query. With hundreds of millions of users, each with thousands of events — including recurring series — recomputing from raw data every time becomes prohibitively expensive. The system needs a smarter representation of availability.
Why "Find a Time" is harder than it looks
Scheduling across multiple people is a set intersection problem on time intervals. Finding where N people are simultaneously free requires intersecting N sets of busy intervals. That's straightforward for 2 people with 5 events each. It becomes expensive when:
Scale factors working against you simultaneously
Event volume per user: A power user might have 5,000+ events per year, including recurring series. A weekly all-hands alone expands to 52 instances. A daily standup creates 260. Querying raw events and expanding recurrences on every scheduling request is O(events × attendees) — expensive.
Concurrency: "Find a Time" is triggered whenever anyone edits attendees on a meeting. At Google's scale, that's millions of scheduling computations per minute across all Calendar users worldwide.
Timezone complexity: DST transitions mean that "9:00 AM Monday" is a different UTC timestamp depending on the timezone — and it shifts twice a year. Cross-timezone scheduling must normalize correctly or produce ghost conflicts.
Realtime edits: If User B accepts a meeting invite while User A is in the "Find Time" flow, the suggestions must reflect the updated state. Stale availability is worse than no availability.
At scale: Google Calendar serves hundreds of millions of users. Each "Find Time" query must intersect availability across N attendees without querying hundreds of events per person from the database. The answer: precompute and cache availability as bitmaps.
The scheduling engine that makes it instant
The system runs on a precomputed bitmap cache backed by an event store. Instead of querying raw events on every request, availability is precomputed per user per day and stored as a compact bitstring. Finding common free slots becomes a bitwise AND operation — O(bits/64) per attendee. Click any node to inspect it.
How each component works
Bitmap-based availability tracking
The core insight: instead of storing events as time ranges, precompute a bitstring per user per day. Each bit represents a time slot (e.g., 15 minutes). A 1 means busy; 0 means free. A full day at 15-minute granularity is 96 bits — just 12 bytes per user per day.
Finding common free slots across N attendees becomes: combined = bitmap_A AND bitmap_B AND bitmap_C… Any 0 in the combined bitmap is a shared free window. This is a single CPU instruction per 64 bits — blindingly fast regardless of how many events each person has.
Timezone normalization
All bitmaps are stored in UTC. When a user in New York checks availability for a meeting with someone in London, both bitmaps are fetched in UTC, ANDed together, then the resulting free windows are converted back to each user's local time for display. This means a single cached bitmap works correctly regardless of which timezone the requester is in.
DST transitions are handled at the conversion layer — the UTC bitmap never changes when clocks spring forward. Only the display offset changes.
Recurring event expansion
Recurring events (RRULE in the iCalendar spec) are stored as a single rule, not as individual instances. When computing a bitmap, the Recurring Event Expander generates only the instances that fall within the requested date window. This is far cheaper than storing and querying every instance individually, especially for daily or weekly recurrences over multi-year periods.
Cache invalidation
Bitmaps are cached in Redis with a 15-minute TTL. Any calendar event mutation (create, update, delete, RSVP change) triggers an invalidation of the affected user's bitmap for affected dates. The next "Find Time" query for that user rebuilds the bitmap from the event store and re-caches it.
This "invalidate on write, rebuild on read" strategy ensures correctness: a bitmap is never stale by more than 15 minutes even without explicit invalidation, and it's updated immediately when the system knows a change occurred.
Working hours and preferences
After the raw bitmap intersection, the scheduling engine applies a working hours mask per attendee. Slots outside 9–5 (per user's configured hours) are filtered out of suggestions. This is another bitmap AND: free_slots AND working_hours_mask. The mask is precomputed once and stored alongside the availability bitmap.
See the bitmap algorithm in action
Each bit represents a 30-minute slot (8:00 AM – 4:00 PM). Click any bit in User A or User B's row to toggle busy/free. The AND result updates instantly — amber bits are shared free windows where a meeting could be scheduled.
Why this is fast: At 15-minute granularity, a full work-week (5 days × 13 hours) is 260 bits — just 5 × 64-bit integers. Finding common availability across 10 attendees is 10 × 5 AND operations = 50 CPU instructions. Compare that to iterating and comparing hundreds of event time ranges in a nested loop.
How the architecture evolves with user count
Drag the slider to see which components are introduced as the scheduling system scales:
Why bitmaps instead of interval trees?
| Approach | Query time | Memory | Update cost | Arbitrary precision? |
|---|---|---|---|---|
| Naive event scan | O(events × attendees) | Low | Zero | Yes |
| Interval tree (sorted) | O(log n + k) | Medium | O(log n) | Yes |
| Segment tree | O(log n) | High | O(log n) | Granularity-limited |
| Bitmap + cache ✓ | O(bits/64) — constant | 12 bytes/user/day | Invalidate + rebuild | Granularity-limited |
Interval trees are more precise — they don't lose resolution at 15-minute boundaries. But for "Find a Time," precision below 15 minutes is irrelevant: no one schedules a meeting at 10:07 AM. The bitmap's fixed granularity is a feature, not a bug — it makes the representation compact, cache-friendly, and trivially intersectable.
The real advantage is cacheability: a bitmap for "User A on 2025-06-01" is a stable cache key. It's valid until User A's events on that date change. An interval tree must be rebuilt from raw events on every query — there's no natural cache key for the partial computation.
How to ace this in a system design interview
-
Design Google Calendar's "Find a Time" feature
- Core problem: find the intersection of free time across N users
- Naive approach (merge intervals per attendee) is O(events × attendees) per request — too slow at scale
- Key insight: separate availability computation from availability querying — precompute as bitmaps
- Precompute: 1 bit = 15-min slot stored in Redis (96 bits/user/day = 12 bytes)
- On "Find Time": fetch N bitmaps → BITOP AND → scan zero-runs → extract free windows
- Cache miss: query event store → expand recurrences → build bitmap → write back to Redis
- Timezones: store all bitmaps in UTC, convert slot positions to local only at display time
- Working hours: AND the result with a precomputed working-hours mask per user
-
How do you handle recurring events efficiently?
- Don't store individual recurring instances — store the RRULE string
- On bitmap computation, the Recurring Event Expander generates only instances within the target date window
- 1 DB row for a weekly meeting generates 52 instances/year — vs 52 stored rows
- Expander is called only on cache miss (bitmap absent from Redis)
- Result is cached — expander not called again until an event mutation invalidates the cache
- Edge case: RRULE exceptions (modified single instances) stored separately and merged during expansion
- EXDATE rules (removed instances) must also be applied during expansion before setting bits
-
How do you handle timezone complexity, including DST?
- Canonical rule: store everything in UTC, display in local — DST complexity lives only in the conversion layer
- On event creation: convert local time ("9 AM America/New_York") to UTC and store that
- On bitmap computation: convert all start/end times to UTC before setting bits
- On display: convert UTC slot positions to the user's local timezone
- DST handled automatically — the UTC representation never changes across DST boundaries
- Edge case: working-hours mask must be recomputed at DST transitions ("9 AM–5 PM local" maps to different UTC slots in summer vs winter)
- Solution: cache the working-hours mask with a version key that rotates at DST transition boundaries
-
How do you handle cross-org calendar access?
- Within-org: full bitmap approach — fetch attendee bitmaps, BITOP AND, extract windows
- Cross-org: external attendees' event details are inaccessible — only free/busy status is exposed (per CalDAV/Exchange standards)
- For external attendees: query the FreeBusy API (RFC 5545 VFREEBUSY) of their calendar server
- FreeBusy returns opaque busy intervals (no event details) → convert directly to bitmap slots
- If the external attendee has shared their calendar directly: full bitmap approach applies
- Fully external with no sharing: Google sends a "Request availability" email → attendee fills a form → free windows processed the same way
What gets stored — and in what form
The data model separates durable event storage (Postgres/Spanner) from ephemeral availability caches (Redis). Only the event store is source of truth — the bitmaps are derived data that can always be recomputed. Click any table to explore its design rationale.
Redis key structure
-- Availability bitmap for a user on a given day (UTC) SET avail:{userId}:{date_utc} <96-bit string, 15min slots> EX 900 -- Working hours mask (9am–5pm in user's timezone, expressed as UTC bits) SET workhours:{userId}:{date_utc}:{tz_offset} <96-bit string> EX 86400 -- Bitwise AND of two users' bitmaps (Redis native command) BITOP AND result_key avail:{userA}:{date} avail:{userB}:{date} -- Count free slots (BITCOUNT on inverted bitmap) BITCOUNT avail:{userId}:{date_utc} -- Invalidate on event mutation (delete + let next read rebuild) DEL avail:{userId}:{affected_date_utc}
Redis BITOP: Redis has a native BITOP AND command that performs bitwise AND across multiple bitstrings in a single command. This means the intersection of N attendees' bitmaps can happen server-side in Redis without fetching all bitmaps to the application layer — reducing both network overhead and application CPU.
The full system at a glance
The HLD shows every major service tier and data flow. Toggle layers to focus on a specific part of the system.
Inside a "Find Time" request — step by step
From clicking "Find a time" to receiving suggestions: walk through every method call, cache check, and computation in sequence.
Every design decision has a cost
The bitmap approach isn't universally correct — it's optimised for Google Calendar's specific constraints. Use the radar to compare approaches, then explore each decision.