Most scheduling tools work the same way under the hood.
They take your available hours, divide them by a slot duration, and hand you a grid of fixed times. 9:00, 9:30, 10:00, 10:30. Pick one.
It works. But it has a fundamental flaw.
The grid assumption
When you pre-generate slots, you make a hard assumption: that every slot boundary is equally valid. But real calendars don’t work that way.
Say you have a rule: available 9am–12pm, 30-minute slots. A calendar event ends at 9:45am. The grid-based approach blocks the entire 9:00 and 9:30 slots, even though there’s a valid 30-minute window from 9:45–10:15 that the guest could book.
You’ve just hidden availability that genuinely exists.
Free windows instead of fixed slots
A better approach: compute free windows, not pre-generated slots.
Instead of dividing your schedule into a grid upfront, you:
- Start with your defined available hours as a continuous interval
- Subtract all busy time — calendar events, existing bookings, buffers, manual blocks, holidays
- What remains is a set of free windows — open intervals of varying length
- At booking time, check: does the requested duration fit entirely inside one of these windows?
This means a guest can start at 9:45, 10:00, 10:15 — any 15-minute mark where their chosen duration fits inside an open window. Not just the pre-generated grid positions.
The implementation difference
Grid approach (simplified):
def get_slots(available_start, available_end, duration_mins):
slots = []
current = available_start
while current + timedelta(minutes=duration_mins) <= available_end:
if not is_busy(current, current + timedelta(minutes=duration_mins)):
slots.append(current)
current += timedelta(minutes=duration_mins)
return slots
Enter fullscreen mode Exit fullscreen mode
Free-window approach:
def get_free_windows(available_start, available_end, busy_periods):
# Start with the full available interval
free = [(available_start, available_end)]
# Subtract each busy period
for busy_start, busy_end in busy_periods:
free = subtract_interval(free, busy_start, busy_end)
# Return windows large enough for at least one slot
return [(s, e) for s, e in free
if (e - s).seconds >= MIN_DURATION_MINS * 60]
Enter fullscreen mode Exit fullscreen mode
The guest-facing slot picker then steps through each free window in 15-minute increments, showing any start time where their chosen duration fits completely inside the window.
Why this matters in practice
The difference becomes obvious on heavy calendar days.
With a grid: a 90-minute meeting ending at 11:30 wipes out the 11:00 and 11:30 slots even if there’s a clear 30-minute window from 11:30–12:00.
With free windows: that 11:30–12:00 window surfaces correctly.
The guest sees it. They can book it.
For hosts with back-to-back schedules — the exact people who most need a scheduling tool — the grid approach quietly hides valid availability. Free windows don’t.
One more thing: confirm-time safety
Computing free windows solves the display problem. But there’s a second problem: what if the calendar changes between when the guest loads the page and when they confirm?
The answer is cache-first with confirm-time re-validation:
- Show availability from a local cache (fast page loads, no live API call on every request)
- At the moment of confirm, re-fetch the host’s calendar live and check the exact interval one more time
- If something changed, reject with a 409 and let the guest pick again
This means a guest can never successfully book a slot that became unavailable while they were on the page — even if the cache was slightly stale.
These are the core ideas behind how we built availability in Skedvio — a scheduling tool that computes free windows rather than a fixed slot grid. If you’re building something similar or just find the problem interesting, I’d love to hear how you’ve approached it.
답글 남기기