Scheduling Is Harder Than It Looks

Appointment booking is the classic underestimated feature. It looks like a form and an insert. Teams routinely quote it in days, and then spend the following month on the parts nobody wrote down.
The difficulty is not the booking. It is that a calendar is shared, mutable state being edited concurrently by people and systems that cannot see each other, across time zones, with a clock that occasionally skips an hour.
Store the intent, not just the instant
The standard advice is to store timestamps in UTC and convert for display. This is correct for events that have already happened and quietly wrong for some events in the future.
A clinic that opens at nine in the morning opens at nine after the clocks change, not at the UTC instant that used to be nine. If you store only the instant, every recurring appointment shifts by an hour twice a year. Recurring rules need the wall-clock time and the time zone name preserved alongside the resolved instant, so the series can be recomputed when the rules change — and time zone rules do change, by government decision, at short notice.
Double-booking is a concurrency problem
Checking whether a slot is free and then writing the booking is two operations, and two users can interleave them. Under light load you may never see it; on the morning a campaign goes out, you will see it repeatedly.
This needs a real constraint — an exclusion constraint on the time range in the database, or a transaction with the appropriate isolation level. It does not need a longer check-then-write, because there is no length of gap that closes the race. Availability shown in the interface is a hint; the database is the arbiter.
Syncing with calendars you do not control
The moment you synchronise with Google Calendar or Outlook, you have adopted someone else's data model, someone else's rate limits, and someone else's opinion about what a recurring event is. Both are well documented and neither behaves quite like the other.
- Sync incrementally with the providers' change tokens. Re-reading whole calendars will exhaust your quota and still miss edits made while you were reading.
- Expect to receive your own writes back as external changes. Tag events you created, or you will build an echo that never settles.
- Decide what wins before you need to know. When both sides changed the same event, silence is not a policy.
- Treat webhooks as unreliable. They are delivered at least once, sometimes late, occasionally not at all — reconcile on a schedule regardless.
- Handle disconnection as a normal state. Tokens are revoked, passwords change, people leave. The queue must drain safely, not pile up.
Budget for it honestly
None of this is exotic, and all of it is work. A booking feature that handles concurrency, recurrence, time zones and two-way sync with an external calendar is a small product in its own right, not a form. Quoting it as a form is how these projects end up late, and the client is rarely the one who was wrong about the scope.



