Availability looks like set arithmetic. Take working hours, subtract busy events, offer what remains. That model is correct in a world with one calendar and one time zone, where one person books at a time. You do not operate in that world, and neither does anyone who shares a booking link.
Free/busy is a federation
Real people hold several calendars: a work account, a personal one, a shared team calendar, sometimes accounts on both Google and Microsoft. A correct answer requires merging all of them, and the merge is where it gets ugly. Providers expose free/busy at different granularity, and tentative events may or may not count as busy. All-day events sit ambiguously across zone boundaries. Two copies of the same meeting can disagree because one of them was declined.
Then decide how fresh the answer has to be. Live fan-out to every provider on every page view is slow and burns rate limits. A cached copy is fast and slightly wrong. The workable design is a synced cache fed by change notifications, plus one live check on the only slot that matters: the one being booked right now.
DST breaks twice a year, on schedule
Store instants in UTC and you still have to render and recur in local time, because humans book "9:00 AM on Tuesdays", not a fixed offset. Twice a year those two views diverge. Spring-forward deletes an hour, so a 2:30 AM slot briefly does not exist; fall-back creates an hour that happens twice. And because the northern and southern hemispheres transition on different dates, a recurring rule defined in one country drifts an hour against invitees in another for weeks at a time.
The failure mode is quiet. Nothing crashes. A booking lands an hour off, weeks after the code shipped, and the bug report will not mention DST because the person reporting it has no idea that is what happened.
Buffers are policy, and policy compounds
A ten-minute buffer after meetings sounds like one subtraction. Now combine it with minimum notice, a daily booking cap, slots aligned to the half hour, and a rule that buffers apply after client calls but not after internal ones. Each rule is trivial alone. Their intersection produces slot lists that surprise the host who configured them, so the engine needs to be able to explain itself: given this two-hour gap, why was nothing offered. Write the policy evaluation as pure functions over explicit inputs, because you will need to replay decisions to answer support tickets.
Two people, one slot
Booking links get shared. Two invitees open the same page, see the same open slot, and click within seconds of each other. If your flow checks availability at page load and books at submit, both bookings succeed and the host is double-booked by your software rather than by accident. The provider will not save you here; calendars happily store overlapping events, because calendars are not lock servers.
You need an atomic step you own. A short hold placed on the slot at selection time, or a serialized final check inside your own database at confirmation, closes the race. Re-reading the provider at submit only narrows it, since two submits can still interleave between read and write. The uniqueness guarantee has to live where you control the transaction.
Treat availability as a distributed system
Because that is what it is: cached copies of remote state with concurrent writers, running on clocks that lie twice a year. Teams that get availability right stop chasing a perfectly fresh answer. They verify at commit time and reconcile continuously, and they keep logs good enough to debug an hour-off booking three weeks after the fact.
Absorbing this class of problem is most of what a scheduling layer such as Horato is for. If you build availability yourself, budget for the distributed system. The set arithmetic is the part you finish in week one.