Notes

27 Sept 2026

How we stop two people booking the same slot

Two customers open a salon’s booking page at the same moment. Both see a 4 pm slot with the same stylist. Both tap Book.

If the system only checks availability when the page loads, both bookings go through. The stylist arrives to find two people, and one of them leaves unhappy. In appointment software this is the failure that matters most, so we treat it as a design constraint rather than a bug to be found later.

Layer 1: the list only offers slots that are free

The list of available times is built from staff working hours, leave, existing appointments and any slots currently on hold. Anything booked or held is not offered. This makes double booking rare, but it is not enough: the list is a snapshot, and it is already out of date the moment it is shown.

Layer 2: check the database again when a slot is chosen

When a customer picks a slot, the server does not trust the list. It looks for any appointment for that staff member that overlaps the requested time and is pending, confirmed or in progress. If one exists, the request fails with a conflict and the customer is asked to choose again.

The same request also enforces a minimum notice period on the server. The interface hides slots that are too close to now, but a client can call the endpoint directly with any time it likes, so the rule has to live where it cannot be skipped.

Layer 3: hold the slot briefly

The database check protects the past. It does not protect the next few minutes, while a customer is choosing services or entering details. So when the check passes, the server takes a short hold.

The hold is a key in Redis built from the branch, the staff member, the date and the start time. It is written with an atomic set-if-absent and an expiry of five minutes. The value records who holds it and a random token. If the key already exists, someone else is holding that slot and the request fails with a conflict. If it does not, the customer gets the key, the token and an expiry time, and the interface shows a countdown.

Two details matter:

  • The hold expires by itself. A customer who closes the tab does not block the slot forever. After five minutes it returns to the list.
  • Only the holder can use it. When the booking request arrives, the server checks that the hold still exists, belongs to that customer and carries the right token. Then it runs the database overlap check once more before creating the appointment.

When a hold is taken or released, the server also broadcasts an event, so other screens showing the same calendar update without a refresh.

What this does not do

The hold is keyed to an exact start time. A booking that starts at 4:15 and overlaps a hold at 4:00 is not blocked by the hold. It is caught by the database check instead. That is deliberate: the hold is a courtesy to the customer who is in the middle of booking, and the database is the source of truth.

It also means the failure mode is honest. The worst case is a customer who is told the slot is no longer available and picks another. That is a small annoyance. A silent double booking is not.

The general lesson

Check where the rule cannot be bypassed, check again at the moment of the write, and give people a short, visible, self-expiring hold while they decide. None of this is exotic, but each layer covers a gap the others leave.