TECHNICAL

Scheduling Is the Third Hardest Problem in Computer Science

February 24, 202614 MIN READ

After naming things and cache invalidation, of course.

The Issue

1:1 mentorship sessions are the product's beating heart. Industry mentors get paired with learners for sessions on Zoom. Simple enough on paper. In reality, you're orchestrating across five timezones, two calendars that refuse to agree, and the universal human tendency to forget meetings that were booked three days ago.

The failure modes are spectacular. Double-book a mentor and two mentees show up to the same Zoom room like an awkward sitcom. Send no reminders and the no-show rate creeps toward 40%. Detect a no-show too late and the other person has already rage-texted their program manager. Scheduling, it turns out, is a distributed systems problem wearing a calendar skin.

The Goal

A scheduling system that: lets mentors define availability slots, prevents conflicts with existing calendar events, sends graduated reminders (4hr -> 30min -> 5min) via WhatsApp and email, detects no-shows via Zoom participant events, and triggers automated follow-up for missed sessions.

Five external API integrations. Five different ways things can break.

The Solution

1. Mentor Slot Definition and Conflict Detection. Mentors define weekly availability windows stored in UserCalendarConfig. When a mentee books a slot, the system runs a two-layer conflict check via UserCalendarBooking.hasConflictingBookingsOrSession(). First, it scans existing platform bookings for time overlaps using interval arithmetic (bookingStartTime < existingEnd && bookingEndTime > existingStart). Then it queries Session.isOverlappingSessionsOfMentor() for any published sessions that collide. Both layers return the conflicting entities, not just a boolean, so the admin dashboard can show what conflicts.

2. Google Calendar Sync. A GoogleCalendarAPI service wraps Google Calendar v3 with full CRUD: create events on booking, update on reschedule, delete on cancellation. OAuth tokens are bootstrapped from an existing GooglePeopleAPI integration, and initializeCalendarClient() handles token refresh transparently. Events include Google Meet links generated via conferenceData.createRequest.

3. Multi-Tier Reminders. A cron job queries upcoming mentorship sessions at three intervals. The query uses a time window with an 11-minute buffer to account for cron drift. Each reminder tier has role-specific WhatsApp templates (MENTORSHIP_SESSION_REMINDER_30_MINUTES_TO_MENTOR vs _TO_MENTEE) with different messaging. Deduplication is handled by SessionReminder: before sending, the cron checks if a record exists for that (sessionId, reminderType) pair. Idempotent by design.

4. No-Show Detection. The MeetingEvent model has an $afterInsert hook that fires on every Zoom webhook event. It watches a whitelist: meeting.started, meeting.participant_joined, and waiting-room variants. When triggered, it runs three queries: did the meeting start? Did the mentor join (matched by @airtribe.live email domain via JSONB query)? Did the mentee join? If meeting started AND mentor joined AND mentee never appeared, WhatsApp the mentee. Vice versa for absent mentors. NotificationTriggerLog.findOneOrInsert() ensures each notification fires exactly once.

5. Attendance Confirmation. Four hours before a session, the mentee's WhatsApp reminder includes interactive buttons. A separate cron checks for replies by tracing the parentMessageId chain in WhatsappMessage. No reply two hours before? Slack notification to the ops channel.

Architecture

Loading diagram...

Complexities Faced

Zoom participant identification was the thorniest problem. Zoom's webhook payload buries participant emails in nested JSONB (data.payload.object.participant.email), and mentors often join from institutional accounts that don't match their platform email. The @airtribe.live domain fallback handles host-account joins, but it's a heuristic, not a guarantee.

Reminder deduplication sounds trivial until your cron runs every 5 minutes and the "30-minute window" overlaps across multiple runs. The SessionReminder table acts as a write-once lock. The 11-minute buffer absorbs cron scheduling jitter.

Timezone hell. Mentors in IST, learners in PST, Zoom in UTC -- every time calculation is a potential bug. All internal timestamps are UTC; all display logic is in the client. But "4 hours before the session" means different things when your cron runs in IST and the session was booked in PST.

Session merging added a late twist: when sessions needed to be consolidated, a SessionMergeLog with a mergeId tracks which sessions share meeting info, preventing orphaned Zoom links. The merge runs in a transaction with audit logging.

What I Learned

Integrating five external APIs means five different auth flows, five different failure modes, and five different opinions about what a "timestamp" is. The real engineering isn't in the happy path -- it's in the $afterInsert hook that fires when a mentee joins 30 seconds before the session officially ends, or the WhatsApp message that arrives after the cron already marked them as a no-show.

Build for idempotency first. Make every operation safe to retry. And never trust that a cron job runs exactly when you scheduled it.